Skip to content

Commit 0bf1d54

Browse files
authored
feat: Add counter metrics for consumed cycles (#9922)
Add counter versions of metrics for consumed cycles that are stored in `ReplicatedState`. The existing ones behave like gauges (so their values can go down when prepayments are made and up when refunds are issued) which makes it more challenging for consumers to build automated monitoring tools to perform aggregations over them. By having them monotonically increase, it's easier to calculate rates of change, show aggregates over time etc. The key idea is to introduce a second map of `<CyclesUseCase, NominalCycles>` in the `ReplicatedState` that will only be updated once per use case: either at the payment stage if we know the precise amount or only at refund stage if a prepayment is made with an expected refund later. The second map is quite similar to the existing in all other aspects (how they are stored in checkpoints or how they are exposed as prometheus metrics) besides how the values are updated. A new map is introduced to ease the transition as migrating from the old map to new is non-trivial given that a proper cutoff point needs to be introduced to handle outstanding callbacks that might have been created before the metric introduction. This is left for a follow-up if and when people decide to do it. The new map will be used in a follow up that will implement the [new management canister endpoint](dfinity/portal#6217) to retrieve canister level metrics. Additionally, the new metrics include the use case `HttpsOutcalls` in the canister level metrics as it's useful to determine how much each canister uses this feature. I've opted to not change existing metrics to do the same as it would make things less clean imo than the current approach -- a single specific API is used to perform this update in exactly one place where it's needed. The changes in the PR are mostly driven by the addition of the new map of metrics, updates in protobuf files to store the new metrics, the changes to support having the `HttpsOutcalls` use case additionally included as well as some changes in tests to support the new metrics.
1 parent 2f5d582 commit 0bf1d54

18 files changed

Lines changed: 479 additions & 102 deletions

File tree

rs/execution_environment/src/execution/response/tests.rs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,25 +98,30 @@ fn execute_response_refunds_cycles() {
9898

9999
// Canister A calls canister B.
100100
let cycles_sent = Cycles::new(1_000_000);
101+
let b_callback = wasm()
102+
.accept_cycles(cycles_sent / 2_u64)
103+
.message_payload()
104+
.append_and_reply()
105+
.build();
101106
let wasm_payload = wasm()
102-
.call_with_cycles(b_id, "update", call_args(), cycles_sent)
107+
.call_with_cycles(
108+
b_id,
109+
"update",
110+
call_args().other_side(b_callback.clone()),
111+
cycles_sent,
112+
)
103113
.build();
104114

105115
// Enqueue ingress message to canister A and execute it.
106116
let msg_id = test.ingress_raw(a_id, "update", wasm_payload).0;
107117
assert_matches!(test.ingress_state(&msg_id), IngressState::Received);
108118
test.execute_message(a_id);
109119

110-
// Create response from canister B to canister A.
111-
let response = ResponseBuilder::new()
112-
.originator(a_id)
113-
.respondent(b_id)
114-
.originator_reply_callback(CallbackId::from(1))
115-
.refund(cycles_sent / 2_u64)
116-
.build();
117-
let response_payload_size = response.payload_size_bytes();
120+
// Execute message on B.
121+
test.induct_messages();
122+
test.execute_message(b_id);
118123

119-
// Execute response.
124+
// Execute response on A.
120125
let balance_before = test.canister_state(a_id).system_state.balance();
121126
let consumed_cycles_before = *test
122127
.canister_state(a_id)
@@ -125,8 +130,16 @@ fn execute_response_refunds_cycles() {
125130
.consumed_cycles_by_use_cases()
126131
.get(&CyclesUseCase::RequestAndResponseTransmission)
127132
.unwrap();
133+
let consumed_cycles_before_counter = *test
134+
.canister_state(a_id)
135+
.system_state
136+
.canister_metrics()
137+
.consumed_cycles_by_use_cases_as_counters()
138+
.get(&CyclesUseCase::RequestAndResponseTransmission)
139+
.unwrap();
128140
let instructions_before = test.canister_executed_instructions(a_id);
129-
test.execute_response(a_id, response);
141+
test.induct_messages();
142+
test.execute_message(a_id);
130143
let instructions_after = test.canister_executed_instructions(a_id);
131144
let instructions_executed = instructions_after - instructions_before;
132145
let balance_after = test.canister_state(a_id).system_state.balance();
@@ -137,6 +150,13 @@ fn execute_response_refunds_cycles() {
137150
.consumed_cycles_by_use_cases()
138151
.get(&CyclesUseCase::RequestAndResponseTransmission)
139152
.unwrap();
153+
let consumed_cycles_after_counter = *test
154+
.canister_state(a_id)
155+
.system_state
156+
.canister_metrics()
157+
.consumed_cycles_by_use_cases_as_counters()
158+
.get(&CyclesUseCase::RequestAndResponseTransmission)
159+
.unwrap();
140160

141161
// The balance is equivalent to the amount of cycles before executing`execute_response`
142162
// plus the unaccepted cycles (no more the cycles sent via request),
@@ -146,7 +166,7 @@ fn execute_response_refunds_cycles() {
146166
let prepayment_for_response_transmission =
147167
mgr.prepayment_for_response_transmission(test.subnet_size(), cost_schedule);
148168
let actual_response_transmission_fee = mgr.xnet_call_bytes_transmitted_fee(
149-
response_payload_size,
169+
NumBytes::from(b_callback.len() as u64),
150170
test.subnet_size(),
151171
cost_schedule,
152172
);
@@ -177,6 +197,13 @@ fn execute_response_refunds_cycles() {
177197
consumed_cycles_after,
178198
consumed_cycles_before - response_transmission_refund.nominal(),
179199
);
200+
assert_eq!(
201+
consumed_cycles_after_counter,
202+
consumed_cycles_before_counter
203+
+ (test.call_fee("update", &b_callback) + actual_response_transmission_fee)
204+
.nominal(),
205+
);
206+
assert_eq!(consumed_cycles_after, consumed_cycles_after_counter);
180207
}
181208
}
182209

rs/execution_environment/src/execution_environment.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,8 +2178,11 @@ impl ExecutionEnvironment {
21782178
state.metadata.subnet_call_context_manager.push_context(
21792179
SubnetCallContext::CanisterHttpRequest(canister_http_request_context),
21802180
);
2181-
if let Some(canister_stats) = state.canister_state_make_mut(&request.sender) {
2182-
canister_stats
2181+
if let Some(canister_state) = state.canister_state_make_mut(&request.sender) {
2182+
canister_state
2183+
.system_state
2184+
.observe_consumed_cycles_for_https_outcall(nominal_http_request_fee);
2185+
canister_state
21832186
.system_state
21842187
.canister_metrics_mut()
21852188
.load_metrics_mut()

0 commit comments

Comments
 (0)