use super::{
MemoryManagerLayoutError, MemoryRuntime, RuntimeDiagnosticError, RuntimeLifecycle, layout,
};
use crate::{DiagnosticMemorySize, MEMORY_MANAGER_LEDGER_ID, WASM_PAGE_SIZE_BYTES};
use ic_stable_structures::Memory;
use serde::Serialize;
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub enum AllocationBinding {
Current { stable_key: String, owner: String },
Ledger,
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocation {
pub memory_manager_id: u8,
pub binding: AllocationBinding,
pub pool_eligible: Option<bool>,
pub virtual_extent: DiagnosticMemorySize,
pub allocated_buckets: u16,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocations {
pub summary: MemoryAllocationSummary,
pub memories: Vec<MemoryAllocation>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct MemoryBindingSummary {
pub allocated_buckets: u16,
pub virtual_extent: DiagnosticMemorySize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocationSummary {
pub current_generation: Option<u64>,
pub bucket_size_pages: u16,
pub allocated_buckets: u16,
pub physical_extent: DiagnosticMemorySize,
pub virtual_extent: DiagnosticMemorySize,
pub current_binding: MemoryBindingSummary,
pub ledger_binding: MemoryBindingSummary,
pub unknown_binding: MemoryBindingSummary,
}
impl MemoryAllocation {
#[must_use]
pub const fn allocated_bytes(&self, bucket_size_pages: u16) -> u64 {
self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
}
#[must_use]
pub const fn bucket_slack_bytes(&self, bucket_size_pages: u16) -> u64 {
self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
}
}
impl MemoryBindingSummary {
#[must_use]
pub const fn allocated_bytes(self, bucket_size_pages: u16) -> u64 {
self.allocated_buckets as u64 * bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
}
#[must_use]
pub const fn bucket_slack_bytes(self, bucket_size_pages: u16) -> u64 {
self.allocated_bytes(bucket_size_pages) - self.virtual_extent.bytes()
}
}
impl MemoryAllocationSummary {
pub const MANAGER_LAYOUT_VERSION: u8 = 1;
pub const BUCKET_CAPACITY: u32 = layout::BUCKET_CAPACITY as u32;
pub const MANAGER_METADATA_BYTES: u64 = WASM_PAGE_SIZE_BYTES;
pub const MANAGER_HEADER_BYTES: u64 = layout::HEADER_BYTES as u64;
pub const MANAGER_BUCKET_TABLE_BYTES: u64 = layout::BUCKETS as u64;
pub const MANAGER_PADDING_BYTES: u64 = WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64;
pub const METADATA_BYTES_READ: u64 = layout::METADATA_BYTES as u64;
pub const MEMORIES_MEASURED: u16 = crate::MEMORY_MANAGER_INVALID_ID as u16;
#[must_use]
pub const fn bucket_size_bytes(&self) -> u64 {
self.bucket_size_pages as u64 * WASM_PAGE_SIZE_BYTES
}
#[must_use]
pub const fn remaining_buckets(&self) -> u32 {
Self::BUCKET_CAPACITY - self.allocated_buckets as u32
}
#[must_use]
pub const fn maximum_bucket_bytes(&self) -> u64 {
Self::BUCKET_CAPACITY as u64 * self.bucket_size_bytes()
}
#[must_use]
pub const fn allocated_bucket_bytes(&self) -> u64 {
self.allocated_buckets as u64 * self.bucket_size_bytes()
}
#[must_use]
pub const fn bucket_slack_bytes(&self) -> u64 {
self.allocated_bucket_bytes() - self.virtual_extent.bytes()
}
#[must_use]
pub const fn known_binding_bytes(&self) -> u64 {
(self.current_binding.allocated_buckets as u64
+ self.ledger_binding.allocated_buckets as u64)
* self.bucket_size_bytes()
}
#[must_use]
pub const fn unknown_binding_bytes(&self) -> u64 {
self.unknown_binding.allocated_bytes(self.bucket_size_pages)
}
#[must_use]
pub const fn unmanaged_bytes(&self) -> u64 {
self.physical_extent.bytes() - Self::MANAGER_METADATA_BYTES - self.allocated_bucket_bytes()
}
}
impl<M: Memory> MemoryRuntime<M> {
pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
let (measured, summary) = self.measure_allocations()?;
let mut memories = Vec::with_capacity(layout::IDS);
for id in 0..255_u8 {
let index = usize::from(id);
let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
memories.push(MemoryAllocation {
memory_manager_id: id,
binding: AllocationBinding::Unknown,
pool_eligible: (id <= crate::MEMORY_MANAGER_GOVERNANCE_MAX_ID).then_some(false),
virtual_extent,
allocated_buckets: measured.buckets[index],
});
}
if let RuntimeLifecycle::Bootstrapped {
binding,
committed_allocations,
} = &self.lifecycle
{
for declaration in committed_allocations.declarations() {
let id = declaration.slot().id();
memories[usize::from(id)].binding = AllocationBinding::Current {
stable_key: declaration.stable_key().as_str().to_string(),
owner: binding
.pool
.authority_for_key(declaration.stable_key())
.expect("committed key has an admitted owner")
.to_string(),
};
}
for memory in &mut memories {
memory.pool_eligible = Some(binding.pool.contains(memory.memory_manager_id));
}
}
memories[usize::from(MEMORY_MANAGER_LEDGER_ID)].binding = AllocationBinding::Ledger;
Ok(MemoryAllocations { summary, memories })
}
pub fn memory_allocation_summary(
&self,
) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
self.measure_allocations().map(|(_, summary)| summary)
}
fn allocation_declarations(&self) -> Option<&[crate::AllocationDeclaration]> {
match &self.lifecycle {
RuntimeLifecycle::Unbootstrapped => None,
RuntimeLifecycle::Bootstrapped {
committed_allocations,
..
} => Some(committed_allocations.declarations()),
}
}
fn measure_allocations(
&self,
) -> Result<(layout::Layout, MemoryAllocationSummary), RuntimeDiagnosticError> {
let declarations = self.allocation_declarations();
let measured = layout::read(self.growth.backing.as_ref())?;
let live_buckets = self
.growth
.allocated_buckets
.try_borrow()
.map_err(|_| RuntimeDiagnosticError::ReentrantAccess)?;
if measured.bucket_pages != self.growth.bucket_size_pages
|| measured.allocated_buckets != *live_buckets
{
return Err(super::RuntimeConstructionError::Layout(
MemoryManagerLayoutError::RuntimeMismatch,
)
.into());
}
let mut current = [false; layout::IDS];
if let Some(snapshot) = declarations {
for declaration in snapshot {
let id = declaration.slot().id();
current[usize::from(id)] = true;
}
}
let mut groups = [MemoryBindingSummary::default(); 3];
let mut total_pages = 0;
for id in 0..255_u8 {
let index = usize::from(id);
if self.memory_size_pages(id) != measured.pages[index] {
return Err(super::RuntimeConstructionError::Layout(
MemoryManagerLayoutError::RuntimeMismatch,
)
.into());
}
let group = if id == MEMORY_MANAGER_LEDGER_ID {
1
} else if current[index] {
0
} else {
2
};
groups[group].allocated_buckets += measured.buckets[index];
groups[group].virtual_extent.wasm_pages += measured.pages[index];
total_pages += measured.pages[index];
}
let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
let summary = MemoryAllocationSummary {
current_generation: self
.committed_allocations()
.ok()
.map(crate::CommittedAllocations::generation),
bucket_size_pages: measured.bucket_pages,
allocated_buckets: measured.allocated_buckets,
physical_extent,
virtual_extent,
current_binding: groups[0],
ledger_binding: groups[1],
unknown_binding: groups[2],
};
Ok((measured, summary))
}
}