use super::{
MemoryManagerLayoutError, MemoryRuntime, RuntimeDiagnosticError, RuntimeLifecycle, layout,
};
use crate::{
DiagnosticMemorySize, IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_STABLE_KEY,
MEMORY_MANAGER_LEDGER_ID, MemoryManagerRangeMode, 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 { stable_key: String, owner: String },
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct AllocationRangeClaim {
pub authority: String,
pub mode: MemoryManagerRangeMode,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocation {
pub memory_manager_id: u8,
pub binding: AllocationBinding,
pub range_claim: Option<AllocationRangeClaim>,
pub virtual_extent: DiagnosticMemorySize,
pub allocated_buckets: u16,
pub allocated_bytes: u64,
pub bucket_slack_bytes: u64,
pub payload_bytes: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocations {
pub current_generation: Option<u64>,
pub manager_layout_version: u8,
pub bucket_size_pages: u16,
pub bucket_size_bytes: u64,
pub bucket_capacity: u32,
pub allocated_buckets: u16,
pub remaining_buckets: u32,
pub maximum_bucket_bytes: u64,
pub physical_extent: DiagnosticMemorySize,
pub virtual_extent: DiagnosticMemorySize,
pub manager_metadata_bytes: u64,
pub manager_header_bytes: u64,
pub manager_bucket_table_bytes: u64,
pub manager_padding_bytes: u64,
pub allocated_bucket_bytes: u64,
pub bucket_slack_bytes: u64,
pub known_binding_bytes: u64,
pub unknown_binding_bytes: u64,
pub unmanaged_bytes: u64,
pub metadata_bytes_read: u64,
pub memories: Vec<MemoryAllocation>,
}
impl<M: Memory> MemoryRuntime<M> {
pub fn memory_allocations(&self) -> Result<MemoryAllocations, RuntimeDiagnosticError> {
let declarations = match &self.lifecycle {
RuntimeLifecycle::Unbootstrapped => None,
RuntimeLifecycle::Bootstrapped { binding, .. } => Some(&binding.declarations),
};
if declarations.is_some_and(|snapshot| {
snapshot.registered_declarations().len() >= layout::IDS
|| snapshot.range_authority().authorities().len() > layout::IDS
}) {
return Err(RuntimeDiagnosticError::AllocationBound);
}
let measured = layout::read(self.backing.as_ref())?;
if measured.bucket_pages != self.bucket_size_pages {
return Err(super::RuntimeConstructionError::Layout(
MemoryManagerLayoutError::RuntimeMismatch,
)
.into());
}
let bucket_size_bytes = u64::from(measured.bucket_pages) * WASM_PAGE_SIZE_BYTES;
let mut memories = Vec::with_capacity(layout::IDS);
let mut total_pages = 0;
let mut known_binding_bytes = 0;
for id in 0..255_u8 {
let index = usize::from(id);
if self.memory(id).size() != measured.pages[index] {
return Err(super::RuntimeConstructionError::Layout(
MemoryManagerLayoutError::RuntimeMismatch,
)
.into());
}
let (binding, range_claim) = current_binding(id, declarations);
let virtual_extent = DiagnosticMemorySize::from_wasm_pages(measured.pages[index]);
let allocated_bytes = u64::from(measured.buckets[index]) * bucket_size_bytes;
if !matches!(binding, AllocationBinding::Unknown) {
known_binding_bytes += allocated_bytes;
}
total_pages += measured.pages[index];
memories.push(MemoryAllocation {
memory_manager_id: id,
binding,
range_claim,
virtual_extent,
allocated_buckets: measured.buckets[index],
allocated_bytes,
bucket_slack_bytes: allocated_bytes - virtual_extent.bytes,
payload_bytes: None,
});
}
let allocated_bucket_bytes = u64::from(measured.allocated_buckets) * bucket_size_bytes;
let physical_extent = DiagnosticMemorySize::from_wasm_pages(measured.physical_pages);
let virtual_extent = DiagnosticMemorySize::from_wasm_pages(total_pages);
Ok(MemoryAllocations {
current_generation: self
.committed_allocations()
.ok()
.map(crate::CommittedAllocations::generation),
manager_layout_version: 1,
bucket_size_pages: measured.bucket_pages,
bucket_size_bytes,
bucket_capacity: 32_768,
allocated_buckets: measured.allocated_buckets,
remaining_buckets: 32_768 - u32::from(measured.allocated_buckets),
maximum_bucket_bytes: 32_768 * bucket_size_bytes,
physical_extent,
virtual_extent,
manager_metadata_bytes: WASM_PAGE_SIZE_BYTES,
manager_header_bytes: layout::HEADER_BYTES as u64,
manager_bucket_table_bytes: layout::BUCKETS as u64,
manager_padding_bytes: WASM_PAGE_SIZE_BYTES - layout::METADATA_BYTES as u64,
allocated_bucket_bytes,
bucket_slack_bytes: allocated_bucket_bytes - virtual_extent.bytes,
known_binding_bytes,
unknown_binding_bytes: allocated_bucket_bytes - known_binding_bytes,
unmanaged_bytes: physical_extent.bytes - WASM_PAGE_SIZE_BYTES - allocated_bucket_bytes,
metadata_bytes_read: layout::METADATA_BYTES as u64,
memories,
})
}
}
fn current_binding(
id: u8,
declarations: Option<&crate::SealedDeclarationSnapshot>,
) -> (AllocationBinding, Option<AllocationRangeClaim>) {
let mut binding = AllocationBinding::Unknown;
let mut range_claim = None;
if let Some(snapshot) = declarations {
if let Some(registration) = snapshot
.registered_declarations()
.iter()
.find(|registration| registration.declaration().slot().memory_manager_id() == Ok(id))
{
binding = AllocationBinding::Current {
stable_key: registration.declaration().stable_key().as_str().to_string(),
owner: registration.authority().to_string(),
};
}
if let Some(claim) = snapshot
.range_authority()
.authorities()
.iter()
.find(|claim| claim.range().contains(id))
{
range_claim = Some(AllocationRangeClaim {
authority: claim.authority().to_string(),
mode: claim.mode(),
});
}
}
if id == MEMORY_MANAGER_LEDGER_ID {
binding = AllocationBinding::Ledger {
stable_key: IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
owner: IC_MEMORY_AUTHORITY_OWNER.to_string(),
};
}
(binding, range_claim)
}