1use super::{
2 MemoryManagerLayoutError, MemoryRuntime, RuntimeDiagnosticError, RuntimeLifecycle, layout,
3};
4use crate::{
5 DiagnosticMemorySize, IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_STABLE_KEY,
6 MEMORY_MANAGER_LEDGER_ID, MemoryManagerRangeMode, WASM_PAGE_SIZE_BYTES,
7};
8use ic_stable_structures::Memory;
9use serde::Serialize;
10
11#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
20pub enum AllocationBinding {
21 Current { stable_key: String, owner: String },
23 Ledger { stable_key: String, owner: String },
25 Unknown,
27}
28
29#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
37pub struct AllocationRangeClaim {
38 pub authority: String,
40 pub mode: MemoryManagerRangeMode,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
54pub struct MemoryAllocation {
55 pub memory_manager_id: u8,
56 pub binding: AllocationBinding,
57 pub range_claim: Option<AllocationRangeClaim>,
58 pub virtual_extent: DiagnosticMemorySize,
59 pub allocated_buckets: u16,
60 pub allocated_bytes: u64,
61 pub bucket_slack_bytes: u64,
62 pub payload_bytes: Option<u64>,
64}
65
66#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
83pub struct MemoryAllocations {
84 pub current_generation: Option<u64>,
86 pub manager_layout_version: u8,
87 pub bucket_size_pages: u16,
89 pub bucket_size_bytes: u64,
90 pub bucket_capacity: u32,
91 pub allocated_buckets: u16,
92 pub remaining_buckets: u32,
93 pub maximum_bucket_bytes: u64,
95 pub physical_extent: DiagnosticMemorySize,
96 pub virtual_extent: DiagnosticMemorySize,
97 pub manager_metadata_bytes: u64,
99 pub manager_header_bytes: u64,
100 pub manager_bucket_table_bytes: u64,
101 pub manager_padding_bytes: u64,
102 pub allocated_bucket_bytes: u64,
103 pub bucket_slack_bytes: u64,
104 pub known_binding_bytes: u64,
105 pub unknown_binding_bytes: u64,
106 pub unmanaged_bytes: u64,
108 pub metadata_bytes_read: u64,
110 pub memories: Vec<MemoryAllocation>,
111}
112
113#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
121pub struct MemoryBindingSummary {
122 pub allocated_bytes: u64,
123 pub bucket_slack_bytes: u64,
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
137pub struct MemoryAllocationSummary {
138 pub current_generation: Option<u64>,
140 pub manager_layout_version: u8,
141 pub bucket_size_pages: u16,
143 pub bucket_size_bytes: u64,
144 pub bucket_capacity: u32,
145 pub allocated_buckets: u16,
146 pub remaining_buckets: u32,
147 pub maximum_bucket_bytes: u64,
149 pub physical_extent: DiagnosticMemorySize,
150 pub virtual_extent: DiagnosticMemorySize,
151 pub manager_metadata_bytes: u64,
153 pub manager_header_bytes: u64,
154 pub manager_bucket_table_bytes: u64,
155 pub manager_padding_bytes: u64,
156 pub allocated_bucket_bytes: u64,
157 pub bucket_slack_bytes: u64,
158 pub known_binding_bytes: u64,
159 pub unknown_binding_bytes: u64,
160 pub unmanaged_bytes: u64,
162 pub metadata_bytes_read: u64,
164 pub memories_measured: u16,
166 pub current_binding: MemoryBindingSummary,
167 pub ledger_binding: MemoryBindingSummary,
168 pub unknown_binding: MemoryBindingSummary,
169}
170
171impl<M: Memory> MemoryRuntime<M> {
172 pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
178 let (measured, summary) = self.measure_allocations()?;
179 let declarations = self.allocation_declarations();
180 let mut memories = Vec::with_capacity(layout::IDS);
181 for id in 0..255_u8 {
182 let index = usize::from(id);
183 let (binding, range_claim) = current_binding(id, declarations);
184 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
185 let allocated_bytes = u64::from(measured.buckets[index]) * summary.bucket_size_bytes;
186 memories.push(MemoryAllocation {
187 memory_manager_id: id,
188 binding,
189 range_claim,
190 virtual_extent,
191 allocated_buckets: measured.buckets[index],
192 allocated_bytes,
193 bucket_slack_bytes: allocated_bytes - virtual_extent.bytes,
194 payload_bytes: None,
195 });
196 }
197 Ok(MemoryAllocations {
198 current_generation: summary.current_generation,
199 manager_layout_version: summary.manager_layout_version,
200 bucket_size_pages: summary.bucket_size_pages,
201 bucket_size_bytes: summary.bucket_size_bytes,
202 bucket_capacity: summary.bucket_capacity,
203 allocated_buckets: summary.allocated_buckets,
204 remaining_buckets: summary.remaining_buckets,
205 maximum_bucket_bytes: summary.maximum_bucket_bytes,
206 physical_extent: summary.physical_extent,
207 virtual_extent: summary.virtual_extent,
208 manager_metadata_bytes: summary.manager_metadata_bytes,
209 manager_header_bytes: summary.manager_header_bytes,
210 manager_bucket_table_bytes: summary.manager_bucket_table_bytes,
211 manager_padding_bytes: summary.manager_padding_bytes,
212 allocated_bucket_bytes: summary.allocated_bucket_bytes,
213 bucket_slack_bytes: summary.bucket_slack_bytes,
214 known_binding_bytes: summary.known_binding_bytes,
215 unknown_binding_bytes: summary.unknown_binding_bytes,
216 unmanaged_bytes: summary.unmanaged_bytes,
217 metadata_bytes_read: summary.metadata_bytes_read,
218 memories,
219 })
220 }
221
222 pub fn memory_allocation_summary(
226 &self,
227 ) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
228 self.measure_allocations().map(|(_, summary)| summary)
229 }
230
231 const fn allocation_declarations(&self) -> Option<&crate::SealedDeclarationSnapshot> {
232 match &self.lifecycle {
234 RuntimeLifecycle::Unbootstrapped => None,
235 RuntimeLifecycle::Bootstrapped { binding, .. } => Some(&binding.declarations),
236 }
237 }
238
239 fn measure_allocations(
240 &self,
241 ) -> Result<(layout::Layout, MemoryAllocationSummary), RuntimeDiagnosticError> {
242 let declarations = self.allocation_declarations();
243 let measured = layout::read(self.growth.backing.as_ref())?;
244 let live_buckets = self
245 .growth
246 .allocated_buckets
247 .try_borrow()
248 .map_err(|_| super::RuntimeStateError::ReentrantAccess)?;
249 if measured.bucket_pages != self.growth.bucket_size_pages
250 || measured.allocated_buckets != *live_buckets
251 {
252 return Err(super::RuntimeConstructionError::Layout(
253 MemoryManagerLayoutError::RuntimeMismatch,
254 )
255 .into());
256 }
257 let bucket_size_bytes = u64::from(measured.bucket_pages) * WASM_PAGE_SIZE_BYTES;
258 let mut current = [false; layout::IDS];
259 if let Some(snapshot) = declarations {
260 for registration in snapshot.registered_declarations() {
261 let id = registration
262 .declaration()
263 .slot()
264 .memory_manager_id()
265 .expect("sealed declaration slot");
266 current[usize::from(id)] = true;
267 }
268 }
269 let mut groups = [MemoryBindingSummary::default(); 3];
270 let mut total_pages = 0;
271 for id in 0..255_u8 {
272 let index = usize::from(id);
273 if self.memory(id).size() != measured.pages[index] {
274 return Err(super::RuntimeConstructionError::Layout(
275 MemoryManagerLayoutError::RuntimeMismatch,
276 )
277 .into());
278 }
279 let group = if id == MEMORY_MANAGER_LEDGER_ID {
280 1
281 } else if current[index] {
282 0
283 } else {
284 2
285 };
286 let allocated_bytes = u64::from(measured.buckets[index]) * bucket_size_bytes;
287 groups[group].allocated_bytes += allocated_bytes;
288 groups[group].bucket_slack_bytes +=
289 allocated_bytes - measured.pages[index] * WASM_PAGE_SIZE_BYTES;
290 total_pages += measured.pages[index];
291 }
292 let allocated_bucket_bytes = u64::from(measured.allocated_buckets) * bucket_size_bytes;
293 let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
294 let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
295 let summary = MemoryAllocationSummary {
296 current_generation: self
297 .committed_allocations()
298 .ok()
299 .map(crate::CommittedAllocations::generation),
300 manager_layout_version: 1,
301 bucket_size_pages: measured.bucket_pages,
302 bucket_size_bytes,
303 bucket_capacity: u32::from(layout::BUCKET_CAPACITY),
304 allocated_buckets: measured.allocated_buckets,
305 remaining_buckets: u32::from(layout::BUCKET_CAPACITY)
306 - u32::from(measured.allocated_buckets),
307 maximum_bucket_bytes: u64::from(layout::BUCKET_CAPACITY) * bucket_size_bytes,
308 physical_extent,
309 virtual_extent,
310 manager_metadata_bytes: WASM_PAGE_SIZE_BYTES,
311 manager_header_bytes: layout::HEADER_BYTES as u64,
312 manager_bucket_table_bytes: layout::BUCKETS as u64,
313 manager_padding_bytes: WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64,
314 allocated_bucket_bytes,
315 bucket_slack_bytes: allocated_bucket_bytes - virtual_extent.bytes,
316 known_binding_bytes: groups[0].allocated_bytes + groups[1].allocated_bytes,
317 unknown_binding_bytes: groups[2].allocated_bytes,
318 unmanaged_bytes: physical_extent.bytes - WASM_PAGE_SIZE_BYTES - allocated_bucket_bytes,
319 metadata_bytes_read: layout::METADATA_BYTES as u64,
320 memories_measured: u16::from(crate::MEMORY_MANAGER_INVALID_ID),
321 current_binding: groups[0],
322 ledger_binding: groups[1],
323 unknown_binding: groups[2],
324 };
325 Ok((measured, summary))
326 }
327}
328
329fn current_binding(
330 id: u8,
331 declarations: Option<&crate::SealedDeclarationSnapshot>,
332) -> (AllocationBinding, Option<AllocationRangeClaim>) {
333 let mut binding = AllocationBinding::Unknown;
334 let mut range_claim = None;
335 if let Some(snapshot) = declarations {
336 if let Some(registration) = snapshot
337 .registered_declarations()
338 .iter()
339 .find(|registration| registration.declaration().slot().memory_manager_id() == Ok(id))
340 {
341 binding = AllocationBinding::Current {
342 stable_key: registration.declaration().stable_key().as_str().to_string(),
343 owner: registration.authority().to_string(),
344 };
345 }
346 if let Some(claim) = snapshot
347 .range_authority()
348 .authorities()
349 .iter()
350 .find(|claim| claim.range().contains(id))
351 {
352 range_claim = Some(AllocationRangeClaim {
353 authority: claim.authority().to_string(),
354 mode: claim.mode(),
355 });
356 }
357 }
358 if id == MEMORY_MANAGER_LEDGER_ID {
359 binding = AllocationBinding::Ledger {
360 stable_key: IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
361 owner: IC_MEMORY_AUTHORITY_OWNER.to_string(),
362 };
363 }
364 (binding, range_claim)
365}