Skip to content

Commit 0907ae4

Browse files
committed
feat: Add detailed memory usage metrics to CanisterStatusResultV2
- Introduced new memory metrics fields in `CanisterStatusResultV2`, including: - `wasm_memory_size` - `stable_memory_size` - `global_memory_size` - `wasm_binary_size` - `custom_sections_size` - `canister_history_size` - `wasm_chunk_store_size` - `snapshots_size` - Updated `CanisterManager` and `CanisterState` to compute and track these new metrics. - Refactored `ExecutionState` to provide individual methods for retrieving specific memory usage values. - Modified tests to validate the new memory metrics in canister status responses. This change improves visibility into canister memory consumption, aiding debugging and resource management. Refer to [PR portal#5240](dfinity/portal#5240) for more details.
1 parent 9dd04a5 commit 0907ae4

File tree

6 files changed

+333
-11
lines changed

6 files changed

+333
-11
lines changed

rs/execution_environment/src/canister_manager.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,14 @@ impl CanisterManager {
10961096
.collect::<Vec<PrincipalId>>();
10971097

10981098
let canister_memory_usage = canister.memory_usage();
1099+
let canister_wasm_memory_usage = canister.wasm_memory_usage();
1100+
let canister_stable_memory_usage = canister.stable_memory_usage();
1101+
let canister_global_memory_usage = canister.global_memory_usage();
1102+
let canister_wasm_binary_memory_usage = canister.wasm_binary_memory_usage();
1103+
let canister_custom_sections_memory_usage = canister.wasm_custom_sections_memory_usage();
1104+
let canister_history_memory_usage = canister.canister_history_memory_usage();
1105+
let canister_wasm_chunk_store_memory_usage = canister.wasm_chunk_store_memory_usage();
1106+
let canister_snapshots_memory_usage = canister.snapshots_memory_usage();
10991107
let canister_message_memory_usage = canister.message_memory_usage();
11001108
let compute_allocation = canister.scheduler_state.compute_allocation;
11011109
let memory_allocation = canister.memory_allocation();
@@ -1114,6 +1122,14 @@ impl CanisterManager {
11141122
*controller,
11151123
controllers,
11161124
canister_memory_usage,
1125+
canister_wasm_memory_usage,
1126+
canister_stable_memory_usage,
1127+
canister_global_memory_usage,
1128+
canister_wasm_binary_memory_usage,
1129+
canister_custom_sections_memory_usage,
1130+
canister_history_memory_usage,
1131+
canister_wasm_chunk_store_memory_usage,
1132+
canister_snapshots_memory_usage,
11171133
canister.system_state.balance().get(),
11181134
compute_allocation.as_percent(),
11191135
Some(memory_allocation.bytes().get()),

rs/execution_environment/src/execution_environment/tests.rs

+172-1
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ use ic_types::{
3535
},
3636
nominal_cycles::NominalCycles,
3737
time::UNIX_EPOCH,
38-
CanisterId, Cycles, PrincipalId, RegistryVersion,
38+
CanisterId, Cycles, PrincipalId, RegistryVersion, CountBytes
3939
};
4040
use ic_types_test_utils::ids::{canister_test_id, node_test_id, subnet_test_id, user_test_id};
4141
use ic_universal_canister::{call_args, wasm, UNIVERSAL_CANISTER_WASM};
4242
use maplit::btreemap;
4343
use std::mem::size_of;
44+
use more_asserts::assert_gt;
4445

4546
#[cfg(test)]
4647
mod canister_task;
@@ -885,6 +886,176 @@ fn get_canister_status_of_nonexisting_canister() {
885886
assert_eq!(ErrorCode::CanisterNotFound, err.code());
886887
}
887888

889+
fn get_canister_status(test: &mut ExecutionTest, canister_id: CanisterId) -> CanisterStatusResultV2 {
890+
match test.canister_status(canister_id).unwrap() {
891+
WasmResult::Reply(reply) => CanisterStatusResultV2::decode(&reply).unwrap(),
892+
WasmResult::Reject(msg) => panic!("{}", msg) ,
893+
}
894+
}
895+
896+
#[test]
897+
fn get_canister_status_memory_metrics() {
898+
let mut test = ExecutionTestBuilder::new().build();
899+
let canister_id = test.universal_canister().unwrap();
900+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
901+
902+
let wasm_memory_size = csr.wasm_memory_size();
903+
let stable_memory_size = csr.stable_memory_size();
904+
let global_memory_size = csr.global_memory_size();
905+
let wasm_binary_size = csr.wasm_binary_size();
906+
let custom_sections_size = csr.custom_sections_size();
907+
908+
let execution_memory_size = wasm_memory_size + stable_memory_size + global_memory_size + wasm_binary_size + custom_sections_size;
909+
910+
let canister_history_size = csr.canister_history_size();
911+
let wasm_chunk_store_size = csr.wasm_chunk_store_size();
912+
let snapshots_size = csr.snapshots_size();
913+
914+
let system_memory_size = canister_history_size + wasm_chunk_store_size + snapshots_size;
915+
916+
let memory_size = csr.memory_size();
917+
assert_eq!(memory_size, execution_memory_size + system_memory_size);
918+
}
919+
920+
#[test]
921+
fn get_canister_status_memory_metrics_wasm_memory_size() {
922+
let mut test = ExecutionTestBuilder::new().build();
923+
let canister_id = test.universal_canister().unwrap();
924+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
925+
926+
assert_eq!(
927+
get_canister_status(&mut test, canister_id).wasm_memory_size(),
928+
csr.wasm_memory_size()
929+
);
930+
931+
let canister_status_args = Encode!(&CanisterIdRecord::from(canister_id)).unwrap();
932+
let get_canister_status = wasm()
933+
.call_simple(
934+
ic00::IC_00,
935+
Method::CanisterStatus,
936+
call_args().other_side(canister_status_args),
937+
)
938+
.build();
939+
let result = test.ingress(canister_id, "update", get_canister_status);
940+
let reply = get_reply(result);
941+
let updated_csr = CanisterStatusResultV2::decode(&reply).unwrap();
942+
943+
assert_gt!(
944+
updated_csr.wasm_memory_size(),
945+
csr.wasm_memory_size()
946+
);
947+
}
948+
949+
#[test]
950+
fn get_canister_status_memory_metrics_stable_memory_size() {
951+
let mut test = ExecutionTestBuilder::new().build();
952+
let canister_id = test.universal_canister().unwrap();
953+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
954+
test.ingress(
955+
canister_id,
956+
"update",
957+
wasm()
958+
.stable64_grow(1)
959+
.stable64_read(WASM_PAGE_SIZE_IN_BYTES as u64 - 1, 1)
960+
.push_bytes(&[])
961+
.append_and_reply()
962+
.build(),
963+
).unwrap();
964+
assert_eq!(
965+
get_canister_status(&mut test, canister_id).stable_memory_size(),
966+
csr.stable_memory_size() + NumBytes::from(WASM_PAGE_SIZE_IN_BYTES as u64)
967+
);
968+
}
969+
970+
#[test]
971+
fn get_canister_status_memory_metrics_global_memory_size() {
972+
let mut test = ExecutionTestBuilder::new().build();
973+
let canister_id = test.universal_canister().unwrap();
974+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
975+
let exported_globals = test.execution_state(canister_id).exported_globals.clone();
976+
assert_eq!(
977+
csr.global_memory_size(),
978+
NumBytes::new(8 * exported_globals.len() as u64)
979+
);
980+
}
981+
982+
#[test]
983+
fn get_canister_status_memory_metrics_wasm_binary_size() {
984+
let mut test = ExecutionTestBuilder::new().build();
985+
let canister_id = test.universal_canister().unwrap();
986+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
987+
assert_eq!(
988+
csr.wasm_binary_size(),
989+
NumBytes::new(UNIVERSAL_CANISTER_WASM.len() as u64)
990+
);
991+
}
992+
993+
#[test]
994+
fn get_canister_status_memory_metrics_custom_sections_size() {
995+
let mut test = ExecutionTestBuilder::new().build();
996+
let canister_id = test.universal_canister().unwrap();
997+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
998+
let metadata = test.execution_state(canister_id).metadata.clone();
999+
assert_eq!(
1000+
csr.custom_sections_size(),
1001+
NumBytes::new(
1002+
metadata.custom_sections()
1003+
.iter()
1004+
.map(|(k, v)| k.len() + v.count_bytes())
1005+
.sum::<usize>() as u64
1006+
),
1007+
);
1008+
}
1009+
1010+
#[test]
1011+
fn get_canister_status_memory_metrics_canister_history_size() {
1012+
let mut test = ExecutionTestBuilder::new().build();
1013+
let canister_id = test.universal_canister().unwrap();
1014+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
1015+
test.set_controller(canister_id, test.user_id().get()).unwrap();
1016+
let memory_difference = NumBytes::from((size_of::<CanisterChange>() + size_of::<PrincipalId>()) as u64);
1017+
assert_eq!(
1018+
get_canister_status(&mut test, canister_id).canister_history_size(),
1019+
csr.canister_history_size() + memory_difference
1020+
);
1021+
}
1022+
1023+
#[test]
1024+
fn get_canister_status_memory_metrics_wasm_chunk_store_size() {
1025+
let mut test = ExecutionTestBuilder::new().build();
1026+
let canister_id = test.universal_canister().unwrap();
1027+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
1028+
test.subnet_message(
1029+
"upload_chunk",
1030+
UploadChunkArgs {
1031+
canister_id: canister_id.into(),
1032+
chunk: vec![1, 2, 3, 4, 5],
1033+
}.encode(),
1034+
).unwrap();
1035+
assert_gt!(
1036+
get_canister_status(&mut test, canister_id).wasm_chunk_store_size(),
1037+
csr.wasm_chunk_store_size()
1038+
);
1039+
}
1040+
1041+
#[test]
1042+
fn get_canister_status_memory_metrics_snapshots_size() {
1043+
let mut test = ExecutionTestBuilder::new().build();
1044+
let canister_id = test.universal_canister().unwrap();
1045+
let csr: CanisterStatusResultV2 = get_canister_status(&mut test, canister_id);
1046+
test.subnet_message(
1047+
"take_canister_snapshot",
1048+
TakeCanisterSnapshotArgs{
1049+
canister_id: canister_id.into(),
1050+
replace_snapshot: None,
1051+
}.encode()
1052+
).unwrap();
1053+
assert_gt!(
1054+
get_canister_status(&mut test, canister_id).snapshots_size(),
1055+
csr.snapshots_size()
1056+
);
1057+
}
1058+
8881059
#[test]
8891060
fn deposit_cycles_to_non_existing_canister_fails() {
8901061
let mut test = ExecutionTestBuilder::new().build();

rs/replica_tests/tests/canister_lifecycle.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ fn can_get_canister_information() {
689689
Ok(WasmResult::Reply(EmptyBlob.encode()))
690690
);
691691

692+
let canister_history_size = NumBytes::from((2 * size_of::<CanisterChange>() + 2 * size_of::<PrincipalId>()) as u64);
692693
// Request the status of canister_b.
693694
assert_matches!(
694695
test.ingress(
@@ -707,7 +708,15 @@ fn can_get_canister_information() {
707708
None,
708709
canister_a.get(),
709710
vec![canister_a.get()],
710-
NumBytes::from((2 * size_of::<CanisterChange>() + 2 * size_of::<PrincipalId>()) as u64),
711+
canister_history_size,
712+
NumBytes::from(0),
713+
NumBytes::from(0),
714+
NumBytes::from(0),
715+
NumBytes::from(0),
716+
NumBytes::from(0),
717+
canister_history_size,
718+
NumBytes::from(0),
719+
NumBytes::from(0),
711720
num_cycles.get(),
712721
ComputeAllocation::default().as_percent(),
713722
None,
@@ -767,6 +776,14 @@ fn can_get_canister_information() {
767776
// We don't assert a specific memory size since the universal canister's
768777
// size changes between updates.
769778
NumBytes::from(0),
779+
NumBytes::from(0),
780+
NumBytes::from(0),
781+
NumBytes::from(0),
782+
NumBytes::from(0),
783+
NumBytes::from(0),
784+
NumBytes::from(0),
785+
NumBytes::from(0),
786+
NumBytes::from(0),
770787
num_cycles.get(),
771788
ComputeAllocation::default().as_percent(),
772789
None,

rs/replicated_state/src/canister_state.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ impl CanisterState {
371371
self.execution_memory_usage()
372372
+ self.canister_history_memory_usage()
373373
+ self.wasm_chunk_store_memory_usage()
374-
+ self.system_state.snapshots_memory_usage
374+
+ self.snapshots_memory_usage()
375375
}
376376

377377
/// Returns the amount of Wasm memory currently used by the canister in bytes.
@@ -381,6 +381,27 @@ impl CanisterState {
381381
.map_or(NumBytes::new(0), |es| es.wasm_memory_usage())
382382
}
383383

384+
/// Returns the amount of stable memory currently used by the canister in bytes.
385+
pub fn stable_memory_usage(&self) -> NumBytes {
386+
self.execution_state
387+
.as_ref()
388+
.map_or(NumBytes::from(0), |es| es.stable_memory_usage())
389+
}
390+
391+
/// Returns the amount of memory currently used by global variables of the canister in bytes.
392+
pub fn global_memory_usage(&self) -> NumBytes {
393+
self.execution_state
394+
.as_ref()
395+
.map_or(NumBytes::from(0), |es| es.global_memory_usage())
396+
}
397+
398+
/// Returns the amount of memory currently used by the wasm binary.
399+
pub fn wasm_binary_memory_usage(&self) -> NumBytes {
400+
self.execution_state
401+
.as_ref()
402+
.map_or(NumBytes::from(0), |es| es.wasm_binary_memory_usage())
403+
}
404+
384405
/// Returns the amount of execution memory (heap, stable, globals, Wasm)
385406
/// currently used by the canister in bytes.
386407
pub fn execution_memory_usage(&self) -> NumBytes {
@@ -412,10 +433,14 @@ impl CanisterState {
412433
}
413434

414435
/// Returns the memory usage of the wasm chunk store in bytes.
415-
pub(super) fn wasm_chunk_store_memory_usage(&self) -> NumBytes {
436+
pub fn wasm_chunk_store_memory_usage(&self) -> NumBytes {
416437
self.system_state.wasm_chunk_store.memory_usage()
417438
}
418439

440+
pub fn snapshots_memory_usage(&self) -> NumBytes {
441+
self.system_state.snapshots_memory_usage
442+
}
443+
419444
/// Returns the snapshot size estimation in bytes based on the current canister's state.
420445
///
421446
/// It represents the memory usage of a snapshot that would be created at the time of the call

rs/replicated_state/src/canister_state/execution_state.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -567,17 +567,37 @@ impl ExecutionState {
567567
.expect("could not convert from wasm memory number of pages to bytes")
568568
}
569569

570-
/// Returns the memory currently used by the `ExecutionState`.
571-
pub fn memory_usage(&self) -> NumBytes {
570+
/// Returns the stable memory currently used by the `ExecutionState`.
571+
pub fn stable_memory_usage(&self) -> NumBytes {
572+
num_bytes_try_from(self.stable_memory.size)
573+
.expect("could not convert from stable memory number of pages to bytes")
574+
}
575+
576+
// Returns the global memory currently used by the `ExecutionState`.
577+
pub fn global_memory_usage(&self) -> NumBytes {
572578
// We use 8 bytes per global.
573579
let globals_size_bytes = 8 * self.exported_globals.len() as u64;
580+
NumBytes::from(globals_size_bytes)
581+
}
582+
583+
// Returns the memory size of the Wasm binary currently used by the `ExecutionState`.
584+
pub fn wasm_binary_memory_usage(&self) -> NumBytes {
574585
let wasm_binary_size_bytes = self.wasm_binary.binary.len() as u64;
586+
NumBytes::from(wasm_binary_size_bytes)
587+
}
588+
589+
// Returns the memory size of the custom sections currently used by the `ExecutionState`.
590+
pub fn custom_sections_memory_size(&self) -> NumBytes {
591+
self.metadata.memory_usage()
592+
}
593+
594+
/// Returns the memory currently used by the `ExecutionState`.
595+
pub fn memory_usage(&self) -> NumBytes {
575596
self.wasm_memory_usage()
576-
+ num_bytes_try_from(self.stable_memory.size)
577-
.expect("could not convert from stable memory number of pages to bytes")
578-
+ NumBytes::from(globals_size_bytes)
579-
+ NumBytes::from(wasm_binary_size_bytes)
580-
+ self.metadata.memory_usage()
597+
+ self.stable_memory_usage()
598+
+ self.global_memory_usage()
599+
+ self.wasm_binary_memory_usage()
600+
+ self.custom_sections_memory_size()
581601
}
582602

583603
/// Returns the number of global variables in the Wasm module.

0 commit comments

Comments
 (0)