Skip to content

feat: Add a monotonic consumed_cycles total to CanisterMetrics - #11465

Merged
mraszyk merged 4 commits into
masterfrom
mraszyk/consumed-cycles-as-counter-field
Sep 8, 2026
Merged

feat: Add a monotonic consumed_cycles total to CanisterMetrics#11465
mraszyk merged 4 commits into
masterfrom
mraszyk/consumed-cycles-as-counter-field

Conversation

@mraszyk

@mraszyk mraszyk commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

CanisterMetrics::consumed_cycles behaves like a gauge: a prepayment raises it and the matching refund lowers it again. That makes it awkward to build monitoring on top of, which is why consumed_cycles_by_use_cases already has a monotonic twin, only bumped once the refund of a prepayment is known.

This adds the same twin for the scalar total: CanisterMetrics::consumed_cycles_monotonic, maintained by observe_consumed_cycles_with_use_case alongside the by-use-case amounts, and persisted in CanisterStateBits (proto field 71).

It is exactly the scalar equivalent of consumed_cycles_by_use_cases_monotonic summed over the use cases that consumed_cycles covers, i.e. everything except HTTPS outcalls, which are only tracked at the subnet level.

The field is absent in checkpoints written before it, and decodes as zero — the same starting point the by-use-case map had when it was introduced. Nothing consumes the new field yet: outside of tests, the only reader is tip.rs, which persists it. No metric or behaviour changes.

A follow-up commit renames the pre-existing as_counter(s) fields to monotonic, at both the canister and the subnet level: neither the CanisterMetrics / CanisterStateBits fields nor the Prometheus metrics they feed are actual counters. It is a pure rename of Rust and protobuf identifiers; the exported Prometheus metric names are deliberately left untouched, so existing dashboards and alerts keep working.

Note on a pre-existing inconsistency that the rename makes visible, and that Copilot flags: in metrics.rs, the subnet-level contribution to the monotonic Prometheus aggregate is joined from get_consumed_cycles_by_use_case(), i.e. the gauge map, not get_consumed_cycles_by_use_case_monotonic(). Since migrate_outcalls_cycles_to_use_cases() deliberately backfills only the gauge map, that join can export the very jump the separate monotonic map exists to avoid. This is unchanged from master (only the accumulator's name differs here), and correcting it would lower the exported HTTP/ECDSA outcall values by the backfilled amount, which reads as a counter reset — so it is left out of this PR.

Follow-ups, kept separate:

  • backfilling consumed_cycles_monotonic from the consumed_cycles gauge, so it starts out with the full history rather than from zero;
  • exporting the monotonic total under the replicated_state_consumed_cycles_since_replica_started gauge.

🤖 Generated with Claude Code

`CanisterMetrics::consumed_cycles` behaves like a gauge: a prepayment
raises it and the matching refund lowers it again. That makes it awkward
to build monitoring on top of, which is why `consumed_cycles_by_use_cases`
already has a monotonic `consumed_cycles_by_use_cases_as_counters` twin,
only bumped once the refund of a prepayment is known.

This adds the same twin for the scalar total, maintained by
`observe_consumed_cycles_with_use_case` alongside the by-use-case
counters and persisted in `CanisterStateBits`. It is exactly the scalar
equivalent of `consumed_cycles_by_use_cases_as_counters` summed over the
use cases that `consumed_cycles` covers, i.e. everything except HTTPS
outcalls, which are only tracked at the subnet level.

The field is absent in checkpoints written before it and decodes as zero,
the same starting point the by-use-case counters had. Nothing reads the
new counter in production yet; backfilling it from the gauge, so that it
starts out with the full history, and exporting it are left to follow-ups.

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

Copilot AI 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.

🟢 Approval recommended

The counter’s accounting, persistence, backward compatibility, and key refund behaviors are implemented consistently and adequately tested.

Pull request overview

Adds a persistent monotonic total for canister-consumed cycles, complementing the existing gauge and per-use-case counters.

Changes:

  • Tracks finalized cycle consumption without decrementing refunds.
  • Persists the counter through checkpoints using protobuf field 71.
  • Tests accounting, serialization, and backward-compatible decoding.
File summaries
File Description
rs/state_manager/src/tip.rs Serializes the counter into checkpoints.
rs/state_manager/src/checkpoint.rs Restores the counter from checkpoints.
rs/state_layout/src/state_layout/tests.rs Tests round-tripping and missing-field defaults.
rs/state_layout/src/state_layout/proto.rs Converts the counter to and from protobuf.
rs/state_layout/src/state_layout.rs Adds the persisted state field.
rs/replicated_state/src/canister_state/tests.rs Tests refundable and final cycle accounting.
rs/replicated_state/src/canister_state/system_state.rs Implements and exposes monotonic tracking.
rs/protobuf/src/gen/state/state.canister_state_bits.v1.rs Adds generated protobuf bindings.
rs/protobuf/def/state/canister_state_bits/v1/canister_state_bits.proto Defines protobuf field 71.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mraszyk
mraszyk marked this pull request as ready for review September 7, 2026 09:40
@mraszyk
mraszyk requested a review from a team as a code owner September 7, 2026 09:40
@zeropath-ai

zeropath-ai Bot commented Sep 7, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 72e78ca.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/execution_environment/src/canister_manager.rs
    Use monotonic consumed_cycles_by_use_cases instead of as_counters
Enhancement ► rs/execution_environment/src/canister_manager/tests.rs
    Update tests to use consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/execution_environment/src/execution/response/tests.rs
    Use consumed_cycles_by_use_cases_monotonic in tests
Enhancement ► rs/execution_environment/src/execution_environment/tests.rs
    Use consumed_cycles_by_use_cases_monotonic in tests
Enhancement ► rs/execution_environment/src/execution_environment/tests/canister_snapshots.rs
    Use consumed_cycles_by_use_cases_monotonic in tests
Enhancement ► rs/execution_environment/src/scheduler/tests/metrics.rs
    Use get_consumed_cycles_by_use_case_monotonic instead of_as_counters
Enhancement ► rs/messaging/src/canister_http_spent.rs
    Use consumed_cycles_by_use_cases_monotonic in tests
Enhancement ► rs/protobuf/def/state/canister_state_bits/v1/canister_state_bits.proto
    Retain consumed_cycles_monotonic field; replace consumed_cycles_by_use_cases_as_counters with consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/protobuf/def/state/metadata/v1/metadata.proto
    Rename consumed_cycles_by_use_case_as_counters to consumed_cycles_by_use_case_monotonic
Enhancement ► rs/protobuf/src/gen/state/state.canister_state_bits.v1.rs
    Add consumed_cycles_monotonic field and replace usage accordingly
Enhancement ► rs/protobuf/src/gen/state/state.metadata.v1.rs
    Rename field to consumed_cycles_by_use_case_monotonic
Enhancement ► rs/replicated_state/src/canister_state/system_state.rs
    Add consumed_cycles_monotonic and corresponding map consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/replicated_state/src/metrics.rs
    Rename metrics field to consumed_cycles_by_use_case_monotonic
Enhancement ► rs/replicated_state/src/metrics.rs (migration/usage)
    Update observe and use of monotonic map
Enhancement ► rs/replicated_state/src/metadata_state.rs
    Replace consumed_cycles_by_use_case_as_counters with consumed_cycles_by_use_case_monotonic
Enhancement ► rs/replicated_state/src/metadata_state/proto.rs
    Map to consumed_cycles_by_use_case_monotonic in proto conversion
Enhancement ► rs/replicated_state/src/metadata_state/tests.rs
    Update tests to use consumed_cycles_by_use_case_monotonic
Enhancement ► rs/state_layout/src/state_layout.rs
    Add consumed_cycles_monotonic field and replace map with consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/state_layout/src/state_layout/proto.rs
    Serialize/deserialize consumed_cycles_monotonic and consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/state_layout/src/state_layout/tests.rs
    Introduce NominalCyclesTesting and set consumed_cycles_monotonic in defaults
Enhancement ► rs/state_manager/src/checkpoint.rs
    Load consumed_cycles_monotonic and consumed_cycles_by_use_cases_monotonic
Enhancement ► rs/state_manager/src/tip.rs
    Serialize consumed_cycles_monotonic and consumed_cycles_by_use_cases_monotonic in checkpoints

@alin-at-dfinity alin-at-dfinity 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.

Only a couple of nitpicks, the only meaningful one being an objection to the "as counter" qualifier.

Comment thread rs/protobuf/def/state/canister_state_bits/v1/canister_state_bits.proto Outdated
Comment thread rs/protobuf/def/state/canister_state_bits/v1/canister_state_bits.proto Outdated
Comment thread rs/replicated_state/src/canister_state/tests.rs Outdated
Comment thread rs/state_layout/src/state_layout/proto.rs Outdated
Comment thread rs/state_layout/src/state_layout.rs Outdated
mraszyk and others added 2 commits September 7, 2026 12:05
…cles-as-counter-field

# Conflicts:
#	rs/replicated_state/src/canister_state/tests.rs
Addresses review feedback: neither the `CanisterMetrics` / `CanisterStateBits`
fields nor the Prometheus metrics they feed are actual counters, so `monotonic`
is the more accurate qualifier. Renames the new `consumed_cycles_as_counter`
along with the pre-existing canister- and subnet-level
`consumed_cycles_by_use_case(s)_as_counters`.

The exported Prometheus metric names are deliberately left untouched, so that
existing dashboards and alerts keep working.

Also, as requested:
 * trims the `consumed_cycles_monotonic` proto comment down to what is specific
   to the field;
 * places it next to `consumed_cycles` everywhere; and
 * calls a charge made without a prepayment a "direct charge", the term already
   used by `consume_cycles`, rather than a "final prepayment".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mraszyk mraszyk changed the title feat: Add a monotonic consumed_cycles_as_counter to CanisterMetrics feat: Add a monotonic consumed_cycles total to CanisterMetrics Sep 7, 2026
@mraszyk
mraszyk requested a balanced review from Copilot September 7, 2026 12:53

Copilot AI 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.

🟡 Changes recommended

The Prometheus monotonic aggregate incorrectly incorporates the subnet gauge map, allowing migration backfills to create spurious counter jumps.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 22/22 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread rs/replicated_state/src/metrics.rs
`refunds_prepayment_of_aborted_canister_install_dropped_after_split` only showed
that the monotonic total stays at zero across a full refund, which is strictly
weaker than `full_refund_does_not_lower_consumed_cycles_monotonic`: the latter
seeds a nonzero total first, so it actually pins down that the refund does not
decrement it. Leaves the test to its own subject, the drop path.

`test_decode_missing_consumed_cycles_monotonic` was a near-duplicate of
`test_encode_decode_consumed_cycles_monotonic`, down to the fixture; folded the
missing-field case into the latter.

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

Copilot AI 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.

🔵 Needs a closer look

The monotonic Prometheus aggregate incorrectly reads the subnet gauge map instead of the monotonic map.

Review details

Suppressed comments (1)

rs/replicated_state/src/metrics.rs:566

  • This aggregate is named and exported as monotonic, but its subnet contribution still comes from get_consumed_cycles_by_use_case(), the gauge map. In particular, migrate_outcalls_cycles_to_use_cases() backfills only that gauge map, so a migration can create the exact spurious jump that the separate monotonic map is intended to avoid; the persisted subnet monotonic values are otherwise never used by this production exporter. Read get_consumed_cycles_by_use_case_monotonic() here instead.
            &mut consumed_cycles_total_by_use_case_monotonic,
  • Files reviewed: 22/22 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@mraszyk
mraszyk added this pull request to the merge queue Sep 8, 2026
Merged via the queue into master with commit a03f3e0 Sep 8, 2026
46 checks passed
@mraszyk
mraszyk deleted the mraszyk/consumed-cycles-as-counter-field branch September 8, 2026 09:49
mraszyk added a commit that referenced this pull request Sep 8, 2026
…cles-as-counter

The base of this branch landed upstream as #11465 with `monotonic` naming in
place of `as_counter`, so the conflicts are almost entirely that rename plus
the review polish it picked up on the way in. Resolved in favour of master
throughout, and renamed the identifiers this branch adds on top to match:

  * `CanisterMetrics::consumed_cycles_as_counter` and the `CanisterStateBits`
    / proto field 71 behind it: dropped this branch's copies, which master
    now carries as `consumed_cycles_monotonic`.
  * `SystemState::migrate_consumed_cycles_to_counter` and the scheduler's
    `migrate_consumed_cycles_to_counters` -> `..._to_monotonic`; the
    `reset_consumed_cycles_as_counter` test helper likewise.
  * `ReplicatedStateMetrics::observe`: this branch's
    `consumed_cycles_by_canisters_as_counter` accumulator ->
    `consumed_cycles_by_canisters_monotonic`, alongside master's
    `consumed_cycles_total_by_use_case_monotonic`.
  * The prose that named the field "the counter" now says "monotonic", and
    the three `canister_state/tests.rs` unit tests this branch and master
    both added are kept once, in master's form.

The exported Prometheus metric names keep their `as_counters` spelling, as
master deliberately left them untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants