Skip to content

Commit 01fcadf

Browse files
feat(nns/sns): Add query stats to canister status (#3710)
# Why The `query_stats` field from the `canister_status` management canister API has been available for a while. Some NNS/SNS canisters expose methods to proxy the management canister method, and they haven't been updated to expose `query_stats` yet. # What - Add `query_stats: QueryStatsFromManagementCanister` for the management canister call. No `opt` is needed since the management canister doesn't have `opt`. - Add `query_stats: opt QueryStats` to the `CanisterStatusResult`/`CanisterStatusResultV2` structs, where each field in `QueryStats` is also `opt`. This is consistent with other NNS/SNS APIs where we use `opt` to allow removing such fields if needed. # Why the API change is safe Since every addition in `*.did` files changed by this PR is `opt`, decoding will always be successful no matter whether the sender/receiver of the message gets the change first.
1 parent 5bea1df commit 01fcadf

File tree

14 files changed

+174
-17
lines changed

14 files changed

+174
-17
lines changed

rs/nervous_system/clients/src/canister_status.rs

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ pub enum LogVisibility {
5454
AllowedViewers(Vec<PrincipalId>),
5555
}
5656

57-
/// Partial copy-paste of ic-types::ic_00::DefiniteCanisterSettings.
57+
/// Partial copy-paste of `ic_management_canister_types::DefiniteCanisterSettings`, and it's used
58+
/// for the response type in the NNS/SNS Root `canister_status` method.
5859
///
59-
/// Only the fields that we need are copied.
60-
/// Candid deserialization is supposed to be tolerant to having data for unknown
61-
/// fields (which is simply discarded).
60+
/// Only the fields that we need are copied. Candid deserialization is supposed to be tolerant to
61+
/// having data for unknown fields (which is simply discarded).
6262
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
6363
pub struct DefiniteCanisterSettings {
6464
pub controllers: Vec<PrincipalId>,
@@ -71,11 +71,11 @@ pub struct DefiniteCanisterSettings {
7171
pub wasm_memory_threshold: Option<candid::Nat>,
7272
}
7373

74-
/// Partial copy-paste of ic-types::ic_00::CanisterStatusResult.
74+
/// Partial copy-paste of `ic_management_canister_types::CanisterStatusResultV2`, and it's used for
75+
/// the response type in the NNS/SNS Root `canister_status` method.
7576
///
76-
/// Only the fields that we need are copied.
77-
/// Candid deserialization is supposed to be tolerant to having data for unknown
78-
/// fields (which are simply discarded).
77+
/// Only the fields that we need are copied. Candid deserialization is supposed to be tolerant to
78+
/// having data for unknown fields (which is simply discarded).
7979
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
8080
pub struct CanisterStatusResult {
8181
pub status: CanisterStatusType,
@@ -86,9 +86,24 @@ pub struct CanisterStatusResult {
8686
pub cycles: candid::Nat,
8787
pub idle_cycles_burned_per_day: Option<candid::Nat>,
8888
pub reserved_cycles: Option<candid::Nat>,
89+
pub query_stats: Option<QueryStats>,
8990
}
9091

91-
/// Copy-paste of `ic_management_canister_types::CanisterStatusResultV2`.
92+
/// Partial copy-paste of `ic_management_canister_types::QueryStats`, and it's used for the response
93+
/// type in the NNS/SNS Root `canister_status` method.
94+
///
95+
/// Only the fields that we need are copied. Candid deserialization is supposed to be tolerant to
96+
/// having data for unknown fields (which is simply discarded).
97+
#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)]
98+
pub struct QueryStats {
99+
pub num_calls_total: Option<candid::Nat>,
100+
pub num_instructions_total: Option<candid::Nat>,
101+
pub request_payload_bytes_total: Option<candid::Nat>,
102+
pub response_payload_bytes_total: Option<candid::Nat>,
103+
}
104+
105+
/// Copy-paste of `ic_management_canister_types::CanisterStatusResultV2`, and it's used for the
106+
/// `canister_status`` method on the management canister.
92107
#[derive(Clone, Eq, PartialEq, Debug, Default, CandidType, Deserialize)]
93108
pub struct CanisterStatusResultFromManagementCanister {
94109
pub status: CanisterStatusType,
@@ -98,13 +113,14 @@ pub struct CanisterStatusResultFromManagementCanister {
98113
pub cycles: candid::Nat,
99114
pub idle_cycles_burned_per_day: candid::Nat,
100115
pub reserved_cycles: candid::Nat,
116+
pub query_stats: QueryStatsFromManagementCanister,
101117
}
102118

103-
/// Partial copy-paste of `ic_management_canister_types::DefiniteCanisterSettingsArgs`.
119+
/// Partial copy-paste of `ic_management_canister_types::DefiniteCanisterSettingsArgs`, and it's
120+
/// used for the response type in the management canister `canister_status` method.
104121
///
105-
/// Only the fields that we need are copied.
106-
/// Candid deserialization is supposed to be tolerant to having data for unknown
107-
/// fields (which is simply discarded).
122+
/// Only the fields that we need are copied. Candid deserialization is supposed to be tolerant to
123+
/// having data for unknown fields (which is simply discarded).
108124
#[derive(Clone, Eq, PartialEq, Debug, Default, CandidType, Deserialize)]
109125
pub struct DefiniteCanisterSettingsFromManagementCanister {
110126
pub controllers: Vec<PrincipalId>,
@@ -117,6 +133,16 @@ pub struct DefiniteCanisterSettingsFromManagementCanister {
117133
pub wasm_memory_threshold: candid::Nat,
118134
}
119135

136+
/// Partial copy-paste of `ic_management_canister_types::QueryStats`, and it's used for the response
137+
/// type in the management canister `canister_status` method.
138+
#[derive(Clone, Eq, PartialEq, Debug, Default, CandidType, Deserialize)]
139+
pub struct QueryStatsFromManagementCanister {
140+
pub num_calls_total: candid::Nat,
141+
pub num_instructions_total: candid::Nat,
142+
pub request_payload_bytes_total: candid::Nat,
143+
pub response_payload_bytes_total: candid::Nat,
144+
}
145+
120146
impl From<CanisterStatusResultFromManagementCanister> for CanisterStatusResult {
121147
fn from(value: CanisterStatusResultFromManagementCanister) -> Self {
122148
let CanisterStatusResultFromManagementCanister {
@@ -127,9 +153,11 @@ impl From<CanisterStatusResultFromManagementCanister> for CanisterStatusResult {
127153
cycles,
128154
idle_cycles_burned_per_day,
129155
reserved_cycles,
156+
query_stats,
130157
} = value;
131158

132159
let settings = DefiniteCanisterSettings::from(settings);
160+
let query_stats = Some(QueryStats::from(query_stats));
133161

134162
let idle_cycles_burned_per_day = Some(idle_cycles_burned_per_day);
135163
let reserved_cycles = Some(reserved_cycles);
@@ -142,6 +170,7 @@ impl From<CanisterStatusResultFromManagementCanister> for CanisterStatusResult {
142170
cycles,
143171
idle_cycles_burned_per_day,
144172
reserved_cycles,
173+
query_stats,
145174
}
146175
}
147176
}
@@ -180,6 +209,29 @@ impl From<DefiniteCanisterSettingsFromManagementCanister> for DefiniteCanisterSe
180209
}
181210
}
182211

212+
impl From<QueryStatsFromManagementCanister> for QueryStats {
213+
fn from(value: QueryStatsFromManagementCanister) -> Self {
214+
let QueryStatsFromManagementCanister {
215+
num_calls_total,
216+
num_instructions_total,
217+
request_payload_bytes_total,
218+
response_payload_bytes_total,
219+
} = value;
220+
221+
let num_calls_total = Some(num_calls_total);
222+
let num_instructions_total = Some(num_instructions_total);
223+
let request_payload_bytes_total = Some(request_payload_bytes_total);
224+
let response_payload_bytes_total = Some(response_payload_bytes_total);
225+
226+
QueryStats {
227+
num_calls_total,
228+
num_instructions_total,
229+
request_payload_bytes_total,
230+
response_payload_bytes_total,
231+
}
232+
}
233+
}
234+
183235
impl CanisterStatusResultFromManagementCanister {
184236
pub fn controllers(&self) -> &[PrincipalId] {
185237
self.settings.controllers.as_slice()
@@ -202,6 +254,12 @@ impl CanisterStatusResultFromManagementCanister {
202254
log_visibility: LogVisibility::Controllers,
203255
wasm_memory_threshold: candid::Nat::from(49_u32),
204256
},
257+
query_stats: QueryStatsFromManagementCanister {
258+
num_calls_total: candid::Nat::from(50_u32),
259+
num_instructions_total: candid::Nat::from(51_u32),
260+
request_payload_bytes_total: candid::Nat::from(52_u32),
261+
response_payload_bytes_total: candid::Nat::from(53_u32),
262+
},
205263
cycles: candid::Nat::from(47_u32),
206264
idle_cycles_burned_per_day: candid::Nat::from(48_u32),
207265
reserved_cycles: candid::Nat::from(49_u32),
@@ -230,6 +288,7 @@ pub struct CanisterStatusResultV2 {
230288
pub cycles: candid::Nat,
231289
// this is for compat with Spec 0.12/0.13
232290
pub idle_cycles_burned_per_day: candid::Nat,
291+
pub query_stats: Option<QueryStats>,
233292
}
234293

235294
impl CanisterStatusResultV2 {
@@ -263,6 +322,12 @@ impl CanisterStatusResultV2 {
263322
Some(wasm_memory_threshold),
264323
),
265324
idle_cycles_burned_per_day: candid::Nat::from(idle_cycles_burned_per_day),
325+
query_stats: Some(QueryStats {
326+
num_calls_total: Some(candid::Nat::from(0_u64)),
327+
num_instructions_total: Some(candid::Nat::from(0_u64)),
328+
request_payload_bytes_total: Some(candid::Nat::from(0_u64)),
329+
response_payload_bytes_total: Some(candid::Nat::from(0_u64)),
330+
}),
266331
}
267332
}
268333

@@ -403,6 +468,12 @@ impl From<CanisterStatusResultFromManagementCanister> for CanisterStatusResultV2
403468
memory_size: value.memory_size,
404469
cycles: value.cycles,
405470
idle_cycles_burned_per_day: value.idle_cycles_burned_per_day,
471+
query_stats: Some(QueryStats {
472+
num_calls_total: Some(value.query_stats.num_calls_total),
473+
num_instructions_total: Some(value.query_stats.num_instructions_total),
474+
request_payload_bytes_total: Some(value.query_stats.request_payload_bytes_total),
475+
response_payload_bytes_total: Some(value.query_stats.response_payload_bytes_total),
476+
}),
406477
}
407478
}
408479
}
@@ -438,6 +509,12 @@ mod tests {
438509
cycles: candid::Nat::from(999_u32),
439510
idle_cycles_burned_per_day: candid::Nat::from(998_u32),
440511
reserved_cycles: candid::Nat::from(997_u32),
512+
query_stats: QueryStatsFromManagementCanister {
513+
num_calls_total: candid::Nat::from(93_u32),
514+
num_instructions_total: candid::Nat::from(92_u32),
515+
request_payload_bytes_total: candid::Nat::from(91_u32),
516+
response_payload_bytes_total: candid::Nat::from(90_u32),
517+
},
441518
};
442519

443520
let expected_canister_status_result = CanisterStatusResult {
@@ -457,6 +534,12 @@ mod tests {
457534
cycles: candid::Nat::from(999_u32),
458535
idle_cycles_burned_per_day: Some(candid::Nat::from(998_u32)),
459536
reserved_cycles: Some(candid::Nat::from(997_u32)),
537+
query_stats: Some(QueryStats {
538+
num_calls_total: Some(candid::Nat::from(93_u32)),
539+
num_instructions_total: Some(candid::Nat::from(92_u32)),
540+
request_payload_bytes_total: Some(candid::Nat::from(91_u32)),
541+
response_payload_bytes_total: Some(candid::Nat::from(90_u32)),
542+
}),
460543
};
461544

462545
let actual_canister_status_result = CanisterStatusResult::from(m);

rs/nervous_system/clients/src/management_canister_client/tests.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::*;
22
use crate::canister_status::{
33
CanisterStatusType, DefiniteCanisterSettingsFromManagementCanister, LogVisibility,
4+
QueryStatsFromManagementCanister,
45
};
56
use candid::Nat;
67
use ic_base_types::{CanisterId, PrincipalId};
@@ -119,6 +120,12 @@ async fn test_limit_outstanding_calls() {
119120
},
120121
status: CanisterStatusType::Running,
121122
reserved_cycles: zero.clone(),
123+
query_stats: QueryStatsFromManagementCanister {
124+
num_calls_total: zero.clone(),
125+
num_instructions_total: zero.clone(),
126+
request_payload_bytes_total: zero.clone(),
127+
response_payload_bytes_total: zero.clone(),
128+
},
122129
};
123130

124131
// Step 2: Call code under test.

rs/nns/handlers/root/impl/canister/root.did

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type CanisterStatusResult = record {
4141
idle_cycles_burned_per_day : opt nat;
4242
module_hash : opt blob;
4343
reserved_cycles : opt nat;
44+
query_stats : opt QueryStats;
4445
};
4546

4647
type CanisterStatusType = variant {
@@ -107,6 +108,13 @@ type CanisterStatusLogVisibility = variant {
107108
allowed_viewers : vec principal;
108109
};
109110

111+
type QueryStats = record {
112+
num_calls_total : opt nat;
113+
num_instructions_total : opt nat;
114+
request_payload_bytes_total : opt nat;
115+
response_payload_bytes_total : opt nat;
116+
};
117+
110118
type StopOrStartCanisterRequest = record {
111119
action : CanisterAction;
112120
canister_id : principal;

rs/nns/handlers/root/interface/src/client.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ic_nervous_system_clients::{
99
canister_id_record::CanisterIdRecord,
1010
canister_status::{
1111
CanisterStatusResult, CanisterStatusType, DefiniteCanisterSettings, LogVisibility,
12+
QueryStats,
1213
},
1314
};
1415
use ic_nns_constants::ROOT_CANISTER_ID;
@@ -226,6 +227,12 @@ impl SpyNnsRootCanisterClientReply {
226227
cycles: candid::Nat::from(42_u32),
227228
idle_cycles_burned_per_day: Some(candid::Nat::from(43_u32)),
228229
reserved_cycles: Some(candid::Nat::from(44_u32)),
230+
query_stats: Some(QueryStats {
231+
num_calls_total: Some(candid::Nat::from(45_u32)),
232+
num_instructions_total: Some(candid::Nat::from(46_u32)),
233+
request_payload_bytes_total: Some(candid::Nat::from(47_u32)),
234+
response_payload_bytes_total: Some(candid::Nat::from(48_u32)),
235+
}),
229236
}))
230237
}
231238

rs/nns/handlers/root/unreleased_changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ on the process that this file is part of, see
99

1010
## Added
1111

12+
* Added the `query_stats` field for the `canister_status` method.
13+
1214
## Changed
1315

14-
- The `LogVisibility` returned from `canister_status` has one more variant `allowed_viewers`,
16+
* The `LogVisibility` returned from `canister_status` has one more variant `allowed_viewers`,
1517
consistent with the corresponding management canister API. Calling `canister_status` for a
1618
canister with such a log visibility setting will no longer panic.
1719

rs/sns/governance/canister/governance.did

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type CanisterStatusResultV2 = record {
5555
settings : DefiniteCanisterSettingsArgs;
5656
idle_cycles_burned_per_day : nat;
5757
module_hash : opt blob;
58+
query_stats : opt QueryStats;
5859
};
5960

6061
type CanisterStatusType = variant {
@@ -610,6 +611,13 @@ type ProposalId = record {
610611
id : nat64;
611612
};
612613

614+
type QueryStats = record {
615+
num_calls_total : opt nat;
616+
num_instructions_total : opt nat;
617+
request_payload_bytes_total : opt nat;
618+
response_payload_bytes_total : opt nat;
619+
};
620+
613621
type RegisterDappCanisters = record {
614622
canister_ids : vec principal;
615623
};

rs/sns/governance/canister/governance_test.did

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type CanisterStatusResultV2 = record {
6464
settings : DefiniteCanisterSettingsArgs;
6565
idle_cycles_burned_per_day : nat;
6666
module_hash : opt blob;
67+
query_stats : opt QueryStats;
6768
};
6869

6970
type CanisterStatusType = variant {
@@ -624,6 +625,13 @@ type ProposalId = record {
624625
id : nat64;
625626
};
626627

628+
type QueryStats = record {
629+
num_calls_total : opt nat;
630+
num_instructions_total : opt nat;
631+
request_payload_bytes_total : opt nat;
632+
response_payload_bytes_total : opt nat;
633+
};
634+
627635
type RegisterDappCanisters = record {
628636
canister_ids : vec principal;
629637
};

rs/sns/governance/unreleased_changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ proposal, e.g.:
3737
```
3838

3939
* Do not redact chunked Wasm data in `ProposalInfo` served from `SnsGov.list_proposals`.
40+
* Added the `query_stats` field for `canister_status`/`get_sns_canisters_summary` methods.
4041

4142
## Changed
4243

rs/sns/root/canister/root.did

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type CanisterStatusResult = record {
2121
idle_cycles_burned_per_day : opt nat;
2222
module_hash : opt blob;
2323
reserved_cycles : opt nat;
24+
query_stats : opt QueryStats;
2425
};
2526

2627
type CanisterStatusResultV2 = record {
@@ -30,6 +31,7 @@ type CanisterStatusResultV2 = record {
3031
settings : DefiniteCanisterSettingsArgs;
3132
idle_cycles_burned_per_day : nat;
3233
module_hash : opt blob;
34+
query_stats : opt QueryStats;
3335
};
3436

3537
type CanisterStatusType = variant {
@@ -80,6 +82,13 @@ type DefiniteCanisterSettingsArgs = record {
8082
wasm_memory_threshold : opt nat;
8183
};
8284

85+
type QueryStats = record {
86+
num_calls_total : opt nat;
87+
num_instructions_total : opt nat;
88+
request_payload_bytes_total : opt nat;
89+
response_payload_bytes_total : opt nat;
90+
};
91+
8392
type FailedUpdate = record {
8493
err : opt CanisterCallError;
8594
dapp_canister_id : opt principal;

rs/sns/root/unreleased_changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ on the process that this file is part of, see
99

1010
## Added
1111

12+
* Added the `query_stats` field for `canister_status`/`get_sns_canisters_summary` methods.
13+
1214
## Changed
1315

14-
- The `LogVisibility` returned from `canister_status` has one more variant `allowed_viewers`,
16+
* The `LogVisibility` returned from `canister_status` has one more variant `allowed_viewers`,
1517
consistent with the corresponding management canister API. Calling `canister_status` for a
1618
canister with such a log visibility setting will no longer panic.
1719

0 commit comments

Comments
 (0)