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 pub allocated_bytes: u64,
46 pub bucket_slack_bytes: u64,
47}
48
49#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
66pub struct MemoryAllocations {
67 pub summary: MemoryAllocationSummary,
69 pub memories: Vec<MemoryAllocation>,
70}
71
72#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
80pub struct MemoryBindingSummary {
81 pub allocated_bytes: u64,
82 pub bucket_slack_bytes: u64,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
96pub struct MemoryAllocationSummary {
97 pub current_generation: Option<u64>,
99 pub manager_layout_version: u8,
100 pub bucket_size_pages: u16,
102 pub bucket_size_bytes: u64,
103 pub bucket_capacity: u32,
104 pub allocated_buckets: u16,
105 pub remaining_buckets: u32,
106 pub maximum_bucket_bytes: u64,
108 pub physical_extent: DiagnosticMemorySize,
109 pub virtual_extent: DiagnosticMemorySize,
110 pub manager_metadata_bytes: u64,
112 pub manager_header_bytes: u64,
113 pub manager_bucket_table_bytes: u64,
114 pub manager_padding_bytes: u64,
115 pub allocated_bucket_bytes: u64,
116 pub bucket_slack_bytes: u64,
117 pub known_binding_bytes: u64,
118 pub unknown_binding_bytes: u64,
119 pub unmanaged_bytes: u64,
121 pub metadata_bytes_read: u64,
123 pub memories_measured: u16,
125 pub current_binding: MemoryBindingSummary,
126 pub ledger_binding: MemoryBindingSummary,
127 pub unknown_binding: MemoryBindingSummary,
128}
129
130impl<M: Memory> MemoryRuntime<M> {
131 pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
141 let (measured, summary) = self.measure_allocations()?;
142 let mut memories = Vec::with_capacity(layout::IDS);
143 for id in 0..255_u8 {
144 let index = usize::from(id);
145 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
146 let allocated_bytes = u64::from(measured.buckets[index]) * summary.bucket_size_bytes;
147 memories.push(MemoryAllocation {
148 memory_manager_id: id,
149 binding: AllocationBinding::Unknown,
150 pool_eligible: (id <= crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID).then_some(false),
151 virtual_extent,
152 allocated_buckets: measured.buckets[index],
153 allocated_bytes,
154 bucket_slack_bytes: allocated_bytes - virtual_extent.bytes,
155 });
156 }
157 if let RuntimeLifecycle::Bootstrapped {
160 binding,
161 committed_allocations,
162 } = &self.lifecycle
163 {
164 for declaration in committed_allocations.declarations() {
165 let id = declaration.slot().id();
166 memories[usize::from(id)].binding = AllocationBinding::Current {
167 stable_key: declaration.stable_key().as_str().to_string(),
168 owner: binding
169 .pool
170 .authority_for_key(declaration.stable_key())
171 .expect("committed key has an admitted owner")
172 .to_string(),
173 };
174 }
175 for memory in &mut memories {
176 memory.pool_eligible = Some(binding.pool.contains(memory.memory_manager_id));
177 }
178 }
179 memories[usize::from(MEMORY_MANAGER_LEDGER_ID)].binding = AllocationBinding::Ledger;
180 Ok(MemoryAllocations { summary, memories })
181 }
182
183 pub fn memory_allocation_summary(
187 &self,
188 ) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
189 self.measure_allocations().map(|(_, summary)| summary)
190 }
191
192 fn allocation_declarations(&self) -> Option<&[crate::AllocationDeclaration]> {
193 match &self.lifecycle {
195 RuntimeLifecycle::Unbootstrapped => None,
196 RuntimeLifecycle::Bootstrapped {
197 committed_allocations,
198 ..
199 } => Some(committed_allocations.declarations()),
200 }
201 }
202
203 fn measure_allocations(
204 &self,
205 ) -> Result<(layout::Layout, MemoryAllocationSummary), RuntimeDiagnosticError> {
206 let declarations = self.allocation_declarations();
207 let measured = layout::read(self.growth.backing.as_ref())?;
208 let live_buckets = self
209 .growth
210 .allocated_buckets
211 .try_borrow()
212 .map_err(|_| super::RuntimeStateError::ReentrantAccess)?;
213 if measured.bucket_pages != self.growth.bucket_size_pages
214 || measured.allocated_buckets != *live_buckets
215 {
216 return Err(super::RuntimeConstructionError::Layout(
217 MemoryManagerLayoutError::RuntimeMismatch,
218 )
219 .into());
220 }
221 let bucket_size_bytes = u64::from(measured.bucket_pages) * WASM_PAGE_SIZE_BYTES;
222 let mut current = [false; layout::IDS];
223 if let Some(snapshot) = declarations {
224 for declaration in snapshot {
225 let id = declaration.slot().id();
226 current[usize::from(id)] = true;
227 }
228 }
229 let mut groups = [MemoryBindingSummary::default(); 3];
230 let mut total_pages = 0;
231 for id in 0..255_u8 {
232 let index = usize::from(id);
233 if self.memory_size_pages(id) != measured.pages[index] {
234 return Err(super::RuntimeConstructionError::Layout(
235 MemoryManagerLayoutError::RuntimeMismatch,
236 )
237 .into());
238 }
239 let group = if id == MEMORY_MANAGER_LEDGER_ID {
240 1
241 } else if current[index] {
242 0
243 } else {
244 2
245 };
246 let allocated_bytes = u64::from(measured.buckets[index]) * bucket_size_bytes;
247 groups[group].allocated_bytes += allocated_bytes;
248 groups[group].bucket_slack_bytes +=
249 allocated_bytes - measured.pages[index] * WASM_PAGE_SIZE_BYTES;
250 total_pages += measured.pages[index];
251 }
252 let allocated_bucket_bytes = u64::from(measured.allocated_buckets) * bucket_size_bytes;
253 let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
254 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
255 let summary = MemoryAllocationSummary {
256 current_generation: self
257 .committed_allocations()
258 .ok()
259 .map(crate::CommittedAllocations::generation),
260 manager_layout_version: 1,
261 bucket_size_pages: measured.bucket_pages,
262 bucket_size_bytes,
263 bucket_capacity: u32::from(layout::BUCKET_CAPACITY),
264 allocated_buckets: measured.allocated_buckets,
265 remaining_buckets: u32::from(layout::BUCKET_CAPACITY)
266 - u32::from(measured.allocated_buckets),
267 maximum_bucket_bytes: u64::from(layout::BUCKET_CAPACITY) * bucket_size_bytes,
268 physical_extent,
269 virtual_extent,
270 manager_metadata_bytes: WASM_PAGE_SIZE_BYTES,
271 manager_header_bytes: layout::HEADER_BYTES as u64,
272 manager_bucket_table_bytes: layout::BUCKETS as u64,
273 manager_padding_bytes: WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64,
274 allocated_bucket_bytes,
275 bucket_slack_bytes: allocated_bucket_bytes - virtual_extent.bytes,
276 known_binding_bytes: groups[0].allocated_bytes + groups[1].allocated_bytes,
277 unknown_binding_bytes: groups[2].allocated_bytes,
278 unmanaged_bytes: physical_extent.bytes - WASM_PAGE_SIZE_BYTES - allocated_bucket_bytes,
279 metadata_bytes_read: layout::METADATA_BYTES as u64,
280 memories_measured: u16::from(crate::MEMORY_MANAGER_INVALID_ID),
281 current_binding: groups[0],
282 ledger_binding: groups[1],
283 unknown_binding: groups[2],
284 };
285 Ok((measured, summary))
286 }
287}