1use super::{
2 MemoryManagerLayoutError, MemoryRuntime, RuntimeDiagnosticError, RuntimeLifecycle, layout,
3};
4use crate::{DiagnosticMemorySize, MEMORY_MANAGER_LEDGER_ID, WASM_PAGE_SIZE_BYTES};
5use ic_stable_structures::Memory;
6use serde::Serialize;
7
8#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
17pub enum AllocationBinding {
18 Current { stable_key: String, owner: String },
20 Ledger,
23 Unknown,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
37pub struct MemoryAllocation {
38 pub memory_manager_id: u8,
39 pub binding: AllocationBinding,
40 pub pool_eligible: Option<bool>,
43 pub virtual_extent: DiagnosticMemorySize,
44 pub allocated_buckets: u16,
45}
46
47#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
64pub struct MemoryAllocations {
65 pub summary: MemoryAllocationSummary,
67 pub memories: Vec<MemoryAllocation>,
68}
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
78pub struct MemoryBindingSummary {
79 pub allocated_buckets: u16,
80 pub virtual_extent: DiagnosticMemorySize,
81}
82
83#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
94pub struct MemoryAllocationSummary {
95 pub current_generation: Option<u64>,
97 pub bucket_size_pages: u16,
99 pub allocated_buckets: u16,
100 pub physical_extent: DiagnosticMemorySize,
101 pub virtual_extent: DiagnosticMemorySize,
102 pub current_binding: MemoryBindingSummary,
103 pub ledger_binding: MemoryBindingSummary,
104 pub unknown_binding: MemoryBindingSummary,
105}
106
107impl MemoryAllocation {
108 #[must_use]
110 pub const fn allocated_bytes(&self, bucket_size_pages: u16) -> u64 {
111 self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
112 }
113
114 #[must_use]
117 pub const fn bucket_slack_bytes(&self, bucket_size_pages: u16) -> u64 {
118 self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
119 }
120}
121
122impl MemoryBindingSummary {
123 #[must_use]
125 pub const fn allocated_bytes(self, bucket_size_pages: u16) -> u64 {
126 self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
127 }
128
129 #[must_use]
132 pub const fn bucket_slack_bytes(self, bucket_size_pages: u16) -> u64 {
133 self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
134 }
135}
136
137impl MemoryAllocationSummary {
138 pub const MANAGER_LAYOUT_VERSION: u8 = 1;
140 pub const BUCKET_CAPACITY: u32 = layout::BUCKET_CAPACITY as u32;
142 pub const MANAGER_METADATA_BYTES: u64 = WASM_PAGE_SIZE_BYTES;
144 pub const MANAGER_HEADER_BYTES: u64 = layout::HEADER_BYTES as u64;
146 pub const MANAGER_BUCKET_TABLE_BYTES: u64 = layout::BUCKETS as u64;
148 pub const MANAGER_PADDING_BYTES: u64 = WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64;
150 pub const METADATA_BYTES_READ: u64 = layout::METADATA_BYTES as u64;
152 pub const MEMORIES_MEASURED: u16 = crate::MEMORY_MANAGER_INVALID_ID as u16;
154
155 #[must_use]
157 pub const fn bucket_size_bytes(&self) -> u64 {
158 self.bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
159 }
160
161 #[must_use]
163 pub const fn remaining_buckets(&self) -> u32 {
164 Self::BUCKET_CAPACITY - self.allocated_buckets as u32
165 }
166
167 #[must_use]
169 pub const fn maximum_bucket_bytes(&self) -> u64 {
170 Self::BUCKET_CAPACITY as u64 * self.bucket_size_bytes()
171 }
172
173 #[must_use]
175 pub const fn allocated_bucket_bytes(&self) -> u64 {
176 self.allocated_buckets as u64 * self.bucket_size_bytes()
177 }
178
179 #[must_use]
181 pub const fn bucket_slack_bytes(&self) -> u64 {
182 self.allocated_bucket_bytes() - self.virtual_extent.bytes()
183 }
184
185 #[must_use]
187 pub const fn known_binding_bytes(&self) -> u64 {
188 (self.current_binding.allocated_buckets as u64
189 + self.ledger_binding.allocated_buckets as u64)
190 * self.bucket_size_bytes()
191 }
192
193 #[must_use]
195 pub const fn unknown_binding_bytes(&self) -> u64 {
196 self.unknown_binding.allocated_bytes(self.bucket_size_pages)
197 }
198
199 #[must_use]
201 pub const fn unmanaged_bytes(&self) -> u64 {
202 self.physical_extent.bytes() - Self::MANAGER_METADATA_BYTES - self.allocated_bucket_bytes()
203 }
204}
205
206impl<M: Memory> MemoryRuntime<M> {
207 pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
217 let (measured, summary) = self.measure_allocations()?;
218 let mut memories = Vec::with_capacity(layout::IDS);
219 for id in 0..255_u8 {
220 let index = usize::from(id);
221 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
222 memories.push(MemoryAllocation {
223 memory_manager_id: id,
224 binding: AllocationBinding::Unknown,
225 pool_eligible: (id <= crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID).then_some(false),
226 virtual_extent,
227 allocated_buckets: measured.buckets[index],
228 });
229 }
230 if let RuntimeLifecycle::Bootstrapped {
233 binding,
234 committed_allocations,
235 } = &self.lifecycle
236 {
237 for declaration in committed_allocations.declarations() {
238 let id = declaration.slot().id();
239 memories[usize::from(id)].binding = AllocationBinding::Current {
240 stable_key: declaration.stable_key().as_str().to_string(),
241 owner: binding
242 .pool
243 .authority_for_key(declaration.stable_key())
244 .expect("committed key has an admitted owner")
245 .to_string(),
246 };
247 }
248 for memory in &mut memories {
249 memory.pool_eligible = Some(binding.pool.contains(memory.memory_manager_id));
250 }
251 }
252 memories[usize::from(MEMORY_MANAGER_LEDGER_ID)].binding = AllocationBinding::Ledger;
253 Ok(MemoryAllocations { summary, memories })
254 }
255
256 pub fn memory_allocation_summary(
260 &self,
261 ) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
262 self.measure_allocations().map(|(_, summary)| summary)
263 }
264
265 fn allocation_declarations(&self) -> Option<&[crate::AllocationDeclaration]> {
266 match &self.lifecycle {
268 RuntimeLifecycle::Unbootstrapped => None,
269 RuntimeLifecycle::Bootstrapped {
270 committed_allocations,
271 ..
272 } => Some(committed_allocations.declarations()),
273 }
274 }
275
276 fn measure_allocations(
277 &self,
278 ) -> Result<(layout::Layout, MemoryAllocationSummary), RuntimeDiagnosticError> {
279 let declarations = self.allocation_declarations();
280 let measured = layout::read(self.growth.backing.as_ref())?;
281 let live_buckets = self
282 .growth
283 .allocated_buckets
284 .try_borrow()
285 .map_err(|_| RuntimeDiagnosticError::ReentrantAccess)?;
286 if measured.bucket_pages != self.growth.bucket_size_pages
287 || measured.allocated_buckets != *live_buckets
288 {
289 return Err(super::RuntimeConstructionError::Layout(
290 MemoryManagerLayoutError::RuntimeMismatch,
291 )
292 .into());
293 }
294 let mut current = [false; layout::IDS];
295 if let Some(snapshot) = declarations {
296 for declaration in snapshot {
297 let id = declaration.slot().id();
298 current[usize::from(id)] = true;
299 }
300 }
301 let mut groups = [MemoryBindingSummary::default(); 3];
302 let mut total_pages = 0;
303 for id in 0..255_u8 {
304 let index = usize::from(id);
305 if self.memory_size_pages(id) != measured.pages[index] {
306 return Err(super::RuntimeConstructionError::Layout(
307 MemoryManagerLayoutError::RuntimeMismatch,
308 )
309 .into());
310 }
311 let group = if id == MEMORY_MANAGER_LEDGER_ID {
312 1
313 } else if current[index] {
314 0
315 } else {
316 2
317 };
318 groups[group].allocated_buckets += measured.buckets[index];
319 groups[group].virtual_extent.wasm_pages += measured.pages[index];
320 total_pages += measured.pages[index];
321 }
322 let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
323 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
324 let summary = MemoryAllocationSummary {
325 current_generation: self
326 .committed_allocations()
327 .ok()
328 .map(crate::CommittedAllocations::generation),
329 bucket_size_pages: measured.bucket_pages,
330 allocated_buckets: measured.allocated_buckets,
331 physical_extent,
332 virtual_extent,
333 current_binding: groups[0],
334 ledger_binding: groups[1],
335 unknown_binding: groups[2],
336 };
337 Ok((measured, summary))
338 }
339}