ic_memory/runtime/
allocations.rs1use super::{MemoryManagerLayoutError, MemoryRuntime, RuntimeDiagnosticError, layout};
2use crate::{DiagnosticMemorySize, MEMORY_MANAGER_LEDGER_ID, WASM_PAGE_SIZE_BYTES};
3use ic_stable_structures::Memory;
4use serde::Serialize;
5
6#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
16pub struct MemoryAllocation {
17 pub memory_manager_id: u8,
18 pub virtual_extent: DiagnosticMemorySize,
19 pub allocated_buckets: u16,
20}
21
22#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
39pub struct MemoryAllocations {
40 pub summary: MemoryAllocationSummary,
42 pub memories: Vec<MemoryAllocation>,
43}
44
45#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
53pub struct MemoryBindingSummary {
54 pub allocated_buckets: u16,
55 pub virtual_extent: DiagnosticMemorySize,
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
69pub struct MemoryAllocationSummary {
70 pub current_generation: Option<u64>,
72 pub bucket_size_pages: u16,
74 pub allocated_buckets: u16,
75 pub physical_extent: DiagnosticMemorySize,
76 pub virtual_extent: DiagnosticMemorySize,
77 pub current_binding: MemoryBindingSummary,
78 pub ledger_binding: MemoryBindingSummary,
79 pub unknown_binding: MemoryBindingSummary,
80}
81
82impl MemoryAllocation {
83 #[must_use]
85 pub const fn allocated_bytes(&self, bucket_size_pages: u16) -> u64 {
86 self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
87 }
88
89 #[must_use]
92 pub const fn bucket_slack_bytes(&self, bucket_size_pages: u16) -> u64 {
93 self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
94 }
95}
96
97impl MemoryBindingSummary {
98 #[must_use]
100 pub const fn allocated_bytes(self, bucket_size_pages: u16) -> u64 {
101 self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
102 }
103
104 #[must_use]
107 pub const fn bucket_slack_bytes(self, bucket_size_pages: u16) -> u64 {
108 self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
109 }
110}
111
112impl MemoryAllocationSummary {
113 pub const MANAGER_LAYOUT_VERSION: u8 = 1;
115 pub const BUCKET_CAPACITY: u32 = layout::BUCKET_CAPACITY as u32;
117 pub const MANAGER_METADATA_BYTES: u64 = WASM_PAGE_SIZE_BYTES;
119 pub const MANAGER_HEADER_BYTES: u64 = layout::HEADER_BYTES as u64;
121 pub const MANAGER_BUCKET_TABLE_BYTES: u64 = layout::BUCKETS as u64;
123 pub const MANAGER_PADDING_BYTES: u64 = WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64;
125 pub const METADATA_BYTES_READ: u64 = layout::METADATA_BYTES as u64;
127 pub const MEMORIES_MEASURED: u16 = crate::MEMORY_MANAGER_INVALID_ID as u16;
129
130 #[must_use]
132 pub const fn bucket_size_bytes(&self) -> u64 {
133 self.bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
134 }
135
136 #[must_use]
138 pub const fn remaining_buckets(&self) -> u32 {
139 Self::BUCKET_CAPACITY - self.allocated_buckets as u32
140 }
141
142 #[must_use]
144 pub const fn maximum_bucket_bytes(&self) -> u64 {
145 Self::BUCKET_CAPACITY as u64 * self.bucket_size_bytes()
146 }
147
148 #[must_use]
150 pub const fn allocated_bucket_bytes(&self) -> u64 {
151 self.allocated_buckets as u64 * self.bucket_size_bytes()
152 }
153
154 #[must_use]
156 pub const fn bucket_slack_bytes(&self) -> u64 {
157 self.allocated_bucket_bytes() - self.virtual_extent.bytes()
158 }
159
160 #[must_use]
162 pub const fn known_binding_bytes(&self) -> u64 {
163 (self.current_binding.allocated_buckets as u64
164 + self.ledger_binding.allocated_buckets as u64)
165 * self.bucket_size_bytes()
166 }
167
168 #[must_use]
170 pub const fn unknown_binding_bytes(&self) -> u64 {
171 self.unknown_binding.allocated_bytes(self.bucket_size_pages)
172 }
173
174 #[must_use]
176 pub const fn unmanaged_bytes(&self) -> u64 {
177 self.physical_extent.bytes() - Self::MANAGER_METADATA_BYTES - self.allocated_bucket_bytes()
178 }
179}
180
181impl<M: Memory> MemoryRuntime<M> {
182 pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
189 let (measured, summary) = self.measure_allocations()?;
190 let mut memories = Vec::with_capacity(layout::IDS);
191 for id in 0..255_u8 {
192 let index = usize::from(id);
193 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
194 memories.push(MemoryAllocation {
195 memory_manager_id: id,
196 virtual_extent,
197 allocated_buckets: measured.buckets[index],
198 });
199 }
200 Ok(MemoryAllocations { summary, memories })
201 }
202
203 pub fn memory_allocation_summary(
207 &self,
208 ) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
209 self.measure_allocations().map(|(_, summary)| summary)
210 }
211
212 fn measure_allocations(
213 &self,
214 ) -> Result<(layout::Layout, MemoryAllocationSummary), RuntimeDiagnosticError> {
215 let declarations = self.committed_allocations().ok();
216 let measured = layout::read(self.growth.backing.as_ref())?;
217 let live_buckets = self
218 .growth
219 .allocated_buckets
220 .try_borrow()
221 .map_err(|_| RuntimeDiagnosticError::ReentrantAccess)?;
222 if measured.bucket_pages != self.growth.bucket_size_pages
223 || measured.allocated_buckets != *live_buckets
224 {
225 return Err(super::RuntimeConstructionError::Layout(
226 MemoryManagerLayoutError::RuntimeMismatch,
227 )
228 .into());
229 }
230 let mut current = [false; layout::IDS];
231 if let Some(snapshot) = declarations {
232 for declaration in snapshot {
233 let id = declaration.slot().id();
234 current[usize::from(id)] = true;
235 }
236 }
237 let mut groups = [MemoryBindingSummary::default(); 3];
238 let mut total_pages = 0;
239 for id in 0..255_u8 {
240 let index = usize::from(id);
241 if self.memory_size_pages(id) != measured.pages[index] {
242 return Err(super::RuntimeConstructionError::Layout(
243 MemoryManagerLayoutError::RuntimeMismatch,
244 )
245 .into());
246 }
247 let group = if id == MEMORY_MANAGER_LEDGER_ID {
248 1
249 } else if current[index] {
250 0
251 } else {
252 2
253 };
254 groups[group].allocated_buckets += measured.buckets[index];
255 groups[group].virtual_extent.wasm_pages += measured.pages[index];
256 total_pages += measured.pages[index];
257 }
258 let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
259 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
260 let summary = MemoryAllocationSummary {
261 current_generation: self.committed_generation().ok(),
262 bucket_size_pages: measured.bucket_pages,
263 allocated_buckets: measured.allocated_buckets,
264 physical_extent,
265 virtual_extent,
266 current_binding: groups[0],
267 ledger_binding: groups[1],
268 unknown_binding: groups[2],
269 };
270 Ok((measured, summary))
271 }
272}