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>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct MemoryBindingSummary {
pub allocated_bytes: u64,
pub bucket_slack_bytes: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct MemoryAllocationSummary {
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_measured: u16,
pub current_binding: MemoryBindingSummary,
pub ledger_binding: MemoryBindingSummary,
pub unknown_binding: MemoryBindingSummary,
}
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]);
let allocated_bytes = u64::from(measured.buckets[index]) * summary.bucket_size_bytes;
memories.push(MemoryAllocation {
memory_manager_id: id,
binding: AllocationBinding::Unknown,
range_claim: None,
virtual_extent,
allocated_buckets: measured.buckets[index],
allocated_bytes,
bucket_slack_bytes: allocated_bytes - virtual_extent.bytes,
payload_bytes: None,
});
}
if let Some(snapshot) = self.allocation_declarations() {
for registration in snapshot.registered_declarations() {
let declaration = registration.declaration();
let id = declaration
.slot()
.memory_manager_id()
.expect("sealed declaration slot");
memories[usize::from(id)].binding = AllocationBinding::Current {
stable_key: declaration.stable_key().as_str().to_string(),
owner: registration.authority().to_string(),
};
}
for claim in snapshot.range_authority().authorities() {
for id in claim.range().start()..=claim.range().end() {
memories[usize::from(id)].range_claim = Some(AllocationRangeClaim {
authority: claim.authority().to_string(),
mode: claim.mode(),
});
}
}
}
memories[usize::from(MEMORY_MANAGER_LEDGER_ID)].binding = AllocationBinding::Ledger {
stable_key: IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
owner: IC_MEMORY_AUTHORITY_OWNER.to_string(),
};
Ok(MemoryAllocations {
current_generation: summary.current_generation,
manager_layout_version: summary.manager_layout_version,
bucket_size_pages: summary.bucket_size_pages,
bucket_size_bytes: summary.bucket_size_bytes,
bucket_capacity: summary.bucket_capacity,
allocated_buckets: summary.allocated_buckets,
remaining_buckets: summary.remaining_buckets,
maximum_bucket_bytes: summary.maximum_bucket_bytes,
physical_extent: summary.physical_extent,
virtual_extent: summary.virtual_extent,
manager_metadata_bytes: summary.manager_metadata_bytes,
manager_header_bytes: summary.manager_header_bytes,
manager_bucket_table_bytes: summary.manager_bucket_table_bytes,
manager_padding_bytes: summary.manager_padding_bytes,
allocated_bucket_bytes: summary.allocated_bucket_bytes,
bucket_slack_bytes: summary.bucket_slack_bytes,
known_binding_bytes: summary.known_binding_bytes,
unknown_binding_bytes: summary.unknown_binding_bytes,
unmanaged_bytes: summary.unmanaged_bytes,
metadata_bytes_read: summary.metadata_bytes_read,
memories,
})
}
pub fn memory_allocation_summary(
&self,
) -> Result<MemoryAllocationSummary, RuntimeDiagnosticError> {
self.measure_allocations().map(|(_, summary)| summary)
}
const fn allocation_declarations(&self) -> Option<&crate::SealedDeclarationSnapshot> {
match &self.lifecycle {
RuntimeLifecycle::Unbootstrapped => None,
RuntimeLifecycle::Bootstrapped { binding, .. } => Some(&binding.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(|_| super::RuntimeStateError::ReentrantAccess)?;
if measured.bucket_pages != self.growth.bucket_size_pages
|| measured.allocated_buckets != *live_buckets
{
return Err(super::RuntimeConstructionError::Layout(
MemoryManagerLayoutError::RuntimeMismatch,
)
.into());
}
let bucket_size_bytes = u64::from(measured.bucket_pages) * WASM_PAGE_SIZE_BYTES;
let mut current = [false; layout::IDS];
if let Some(snapshot) = declarations {
for registration in snapshot.registered_declarations() {
let id = registration
.declaration()
.slot()
.memory_manager_id()
.expect("sealed declaration slot");
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(id).size() != 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
};
let allocated_bytes = u64::from(measured.buckets[index]) * bucket_size_bytes;
groups[group].allocated_bytes += allocated_bytes;
groups[group].bucket_slack_bytes +=
allocated_bytes - measured.pages[index] * WASM_PAGE_SIZE_BYTES;
total_pages += measured.pages[index];
}
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);
let summary = MemoryAllocationSummary {
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: u32::from(layout::BUCKET_CAPACITY),
allocated_buckets: measured.allocated_buckets,
remaining_buckets: u32::from(layout::BUCKET_CAPACITY)
- u32::from(measured.allocated_buckets),
maximum_bucket_bytes: u64::from(layout::BUCKET_CAPACITY) * 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: groups[0].allocated_bytes + groups[1].allocated_bytes,
unknown_binding_bytes: groups[2].allocated_bytes,
unmanaged_bytes: physical_extent.bytes - WASM_PAGE_SIZE_BYTES - allocated_bucket_bytes,
metadata_bytes_read: layout::METADATA_BYTES as u64,
memories_measured: u16::from(crate::MEMORY_MANAGER_INVALID_ID),
current_binding: groups[0],
ledger_binding: groups[1],
unknown_binding: groups[2],
};
Ok((measured, summary))
}
}