#[cfg(test)]
mod tests;
use crate::{db::memory_runtime, error::InternalError};
use ic_memory::MemoryAllocationSummary;
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::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: MemoryExtent,
pub virtual_extent: MemoryExtent,
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 MemoryAllocations {
fn from_runtime<M: ic_memory::Memory>(
runtime: &ic_memory::MemoryRuntime<M>,
) -> Result<Self, InternalError> {
let report = runtime
.memory_allocations()
.map_err(|_| InternalError::store_internal())?;
let (bindings, pool) = if runtime.is_bootstrapped() {
(
runtime
.committed_allocations()
.map_err(|_| InternalError::store_internal())?,
Some(
runtime
.allocation_pool()
.map_err(|_| InternalError::store_internal())?,
),
)
} else {
(&[][..], None)
};
Ok(Self {
current_generation: report.summary.current_generation,
manager_layout_version: MemoryAllocationSummary::MANAGER_LAYOUT_VERSION,
bucket_size_pages: report.summary.bucket_size_pages,
bucket_size_bytes: report.summary.bucket_size_bytes(),
bucket_capacity: MemoryAllocationSummary::BUCKET_CAPACITY,
allocated_buckets: report.summary.allocated_buckets,
remaining_buckets: report.summary.remaining_buckets(),
maximum_bucket_bytes: report.summary.maximum_bucket_bytes(),
physical_extent: report.summary.physical_extent.into(),
virtual_extent: report.summary.virtual_extent.into(),
manager_metadata_bytes: MemoryAllocationSummary::MANAGER_METADATA_BYTES,
manager_header_bytes: MemoryAllocationSummary::MANAGER_HEADER_BYTES,
manager_bucket_table_bytes: MemoryAllocationSummary::MANAGER_BUCKET_TABLE_BYTES,
manager_padding_bytes: MemoryAllocationSummary::MANAGER_PADDING_BYTES,
allocated_bucket_bytes: report.summary.allocated_bucket_bytes(),
bucket_slack_bytes: report.summary.bucket_slack_bytes(),
known_binding_bytes: report.summary.known_binding_bytes(),
unknown_binding_bytes: report.summary.unknown_binding_bytes(),
unmanaged_bytes: report.summary.unmanaged_bytes(),
metadata_bytes_read: MemoryAllocationSummary::METADATA_BYTES_READ,
memories: report
.memories
.into_iter()
.map(|memory| {
MemoryAllocation::from_runtime(
memory,
report.summary.bucket_size_pages,
bindings,
pool,
)
})
.collect::<Result<_, _>>()?,
})
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
pub struct MemoryExtent {
pub wasm_pages: u64,
pub bytes: u64,
}
impl From<ic_memory::DiagnosticMemorySize> for MemoryExtent {
fn from(extent: ic_memory::DiagnosticMemorySize) -> Self {
Self {
wasm_pages: extent.wasm_pages,
bytes: extent.bytes(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
pub enum MemoryAllocationBinding {
Current {
stable_key: String,
owner: String,
},
Ledger {
stable_key: String,
owner: String,
},
Unknown,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
pub struct MemoryAllocation {
pub memory_manager_id: u8,
pub binding: MemoryAllocationBinding,
pub pool_eligible: Option<bool>,
pub virtual_extent: MemoryExtent,
pub allocated_buckets: u16,
pub allocated_bytes: u64,
pub bucket_slack_bytes: u64,
pub payload_bytes: Option<u64>,
}
impl MemoryAllocation {
fn from_runtime(
memory: ic_memory::MemoryAllocation,
bucket_size_pages: u16,
bindings: &[ic_memory::AllocationDeclaration],
pool: Option<&ic_memory::MemoryAllocationPool>,
) -> Result<Self, InternalError> {
let allocated_bytes = memory.allocated_bytes(bucket_size_pages);
let bucket_slack_bytes = memory.bucket_slack_bytes(bucket_size_pages);
let binding = if memory.memory_manager_id == ic_memory::MEMORY_MANAGER_LEDGER_ID {
MemoryAllocationBinding::Ledger {
stable_key: ic_memory::IC_MEMORY_LEDGER_STABLE_KEY.to_string(),
owner: ic_memory::IC_MEMORY_AUTHORITY_OWNER.to_string(),
}
} else if let Some(declaration) = bindings
.iter()
.find(|binding| binding.slot().id() == memory.memory_manager_id)
{
let owner = pool
.and_then(|pool| pool.authority_for_key(declaration.stable_key()))
.ok_or_else(InternalError::store_invariant)?;
MemoryAllocationBinding::Current {
stable_key: declaration.stable_key().to_string(),
owner: owner.to_string(),
}
} else {
MemoryAllocationBinding::Unknown
};
Ok(Self {
memory_manager_id: memory.memory_manager_id,
binding,
pool_eligible: pool.map(|pool| pool.contains(memory.memory_manager_id)),
virtual_extent: memory.virtual_extent.into(),
allocated_buckets: memory.allocated_buckets,
allocated_bytes,
bucket_slack_bytes,
payload_bytes: None,
})
}
}
pub(super) fn collect_memory_allocations() -> Result<Option<MemoryAllocations>, InternalError> {
let Some(runtime) = memory_runtime() else {
return Ok(None);
};
MemoryAllocations::from_runtime(runtime.as_ref()).map(Some)
}