Skip to main content

icydb_core/db/diagnostics/
memory_allocations.rs

1//! Module: diagnostics::memory_allocations
2//! Responsibility: project bounded runtime allocation facts into Candid DTOs.
3//! Does not own: manager validation, allocation policy, or payload accounting.
4//! Boundary: ic-memory's owned report -> canister-wide storage diagnostics.
5
6#[cfg(test)]
7mod tests;
8
9use crate::error::InternalError;
10use candid::CandidType;
11use serde::Deserialize;
12
13/// Physical allocation accounting for the entire default memory runtime.
14///
15/// The ledger is already included in totals. Virtual extent is addressable
16/// memory, not live payload. Unknown bindings still own physical allocations.
17#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
18pub struct MemoryAllocations {
19    /// Committed runtime generation, absent before allocation bootstrap.
20    pub current_generation: Option<u64>,
21    /// Validated manager layout version.
22    pub manager_layout_version: u8,
23    /// Actual persisted bucket size in Wasm pages.
24    pub bucket_size_pages: u16,
25    /// Actual persisted bucket size in bytes.
26    pub bucket_size_bytes: u64,
27    /// Total bucket-table capacity shared by all slots.
28    pub bucket_capacity: u32,
29    /// Number of assigned buckets.
30    pub allocated_buckets: u16,
31    /// Remaining bucket-table entries.
32    pub remaining_buckets: u32,
33    /// Table capacity in bytes, excluding metadata and backing limits.
34    pub maximum_bucket_bytes: u64,
35    /// Entire backing extent, including manager metadata and residuals.
36    pub physical_extent: MemoryExtent,
37    /// Sum of addressable virtual extents.
38    pub virtual_extent: MemoryExtent,
39    /// Complete manager metadata page, including padding.
40    pub manager_metadata_bytes: u64,
41    /// Manager header bytes.
42    pub manager_header_bytes: u64,
43    /// Manager bucket-table bytes.
44    pub manager_bucket_table_bytes: u64,
45    /// Padding inside the manager metadata page.
46    pub manager_padding_bytes: u64,
47    /// Sum of bytes assigned to all slots, including the ledger.
48    pub allocated_bucket_bytes: u64,
49    /// Assigned bucket bytes beyond virtual extents.
50    pub bucket_slack_bytes: u64,
51    /// Assigned bytes with a current binding, including the ledger.
52    pub known_binding_bytes: u64,
53    /// Assigned bytes without a current binding; not free space.
54    pub unknown_binding_bytes: u64,
55    /// Backing bytes outside the manager's assigned region.
56    pub unmanaged_bytes: u64,
57    /// Metadata bytes read by the bounded collector.
58    pub metadata_bytes_read: u64,
59    /// All 255 usable slots in ID order, including zero-size slots.
60    pub memories: Vec<MemoryAllocation>,
61}
62
63impl From<ic_memory::MemoryAllocations> for MemoryAllocations {
64    fn from(report: ic_memory::MemoryAllocations) -> Self {
65        Self {
66            current_generation: report.current_generation,
67            manager_layout_version: report.manager_layout_version,
68            bucket_size_pages: report.bucket_size_pages,
69            bucket_size_bytes: report.bucket_size_bytes,
70            bucket_capacity: report.bucket_capacity,
71            allocated_buckets: report.allocated_buckets,
72            remaining_buckets: report.remaining_buckets,
73            maximum_bucket_bytes: report.maximum_bucket_bytes,
74            physical_extent: report.physical_extent.into(),
75            virtual_extent: report.virtual_extent.into(),
76            manager_metadata_bytes: report.manager_metadata_bytes,
77            manager_header_bytes: report.manager_header_bytes,
78            manager_bucket_table_bytes: report.manager_bucket_table_bytes,
79            manager_padding_bytes: report.manager_padding_bytes,
80            allocated_bucket_bytes: report.allocated_bucket_bytes,
81            bucket_slack_bytes: report.bucket_slack_bytes,
82            known_binding_bytes: report.known_binding_bytes,
83            unknown_binding_bytes: report.unknown_binding_bytes,
84            unmanaged_bytes: report.unmanaged_bytes,
85            metadata_bytes_read: report.metadata_bytes_read,
86            memories: report.memories.into_iter().map(Into::into).collect(),
87        }
88    }
89}
90
91/// Measured extent in 64-KiB Wasm pages and bytes.
92#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
93pub struct MemoryExtent {
94    /// Extent in Wasm pages.
95    pub wasm_pages: u64,
96    /// Extent in bytes.
97    pub bytes: u64,
98}
99
100impl From<ic_memory::DiagnosticMemorySize> for MemoryExtent {
101    fn from(extent: ic_memory::DiagnosticMemorySize) -> Self {
102        Self {
103            wasm_pages: extent.wasm_pages,
104            bytes: extent.bytes,
105        }
106    }
107}
108
109/// Current binding of a physical slot; this grants no opening authority.
110#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
111pub enum MemoryAllocationBinding {
112    /// A declaration bound by successful runtime bootstrap.
113    Current {
114        /// Durable allocation key.
115        stable_key: String,
116        /// Declaring authority.
117        owner: String,
118    },
119    /// Reserved allocation ledger; its payload is not inspected here.
120    Ledger {
121        /// Ledger allocation key.
122        stable_key: String,
123        /// Ledger authority.
124        owner: String,
125    },
126    /// No current binding is available; historical ownership is not decoded.
127    Unknown,
128}
129
130/// Current range policy metadata, not proof of historical slot ownership.
131#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
132pub struct MemoryAllocationRangeClaim {
133    /// Declaring range authority.
134    pub authority: String,
135    /// True for reserved ranges; false for allowed application ranges.
136    pub reserved: bool,
137}
138
139/// Physical accounting for one usable manager slot.
140#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
141pub struct MemoryAllocation {
142    /// Memory-manager slot ID.
143    pub memory_manager_id: u8,
144    /// Current allocation binding, including explicit unknown ownership.
145    pub binding: MemoryAllocationBinding,
146    /// Current range claim, if any.
147    pub range_claim: Option<MemoryAllocationRangeClaim>,
148    /// Addressable virtual extent, not payload occupancy.
149    pub virtual_extent: MemoryExtent,
150    /// Assigned bucket count.
151    pub allocated_buckets: u16,
152    /// Assigned bucket bytes.
153    pub allocated_bytes: u64,
154    /// Assigned capacity beyond the virtual extent.
155    pub bucket_slack_bytes: u64,
156    /// Unavailable: manager metadata cannot measure live payload occupancy.
157    pub payload_bytes: Option<u64>,
158}
159
160impl From<ic_memory::MemoryAllocation> for MemoryAllocation {
161    fn from(memory: ic_memory::MemoryAllocation) -> Self {
162        Self {
163            memory_manager_id: memory.memory_manager_id,
164            binding: match memory.binding {
165                ic_memory::AllocationBinding::Current { stable_key, owner } => {
166                    MemoryAllocationBinding::Current { stable_key, owner }
167                }
168                ic_memory::AllocationBinding::Ledger { stable_key, owner } => {
169                    MemoryAllocationBinding::Ledger { stable_key, owner }
170                }
171                ic_memory::AllocationBinding::Unknown => MemoryAllocationBinding::Unknown,
172            },
173            range_claim: memory.range_claim.map(|claim| MemoryAllocationRangeClaim {
174                authority: claim.authority,
175                reserved: match claim.mode {
176                    ic_memory::MemoryManagerRangeMode::Reserved => true,
177                    ic_memory::MemoryManagerRangeMode::Allowed => false,
178                },
179            }),
180            virtual_extent: memory.virtual_extent.into(),
181            allocated_buckets: memory.allocated_buckets,
182            allocated_bytes: memory.allocated_bytes,
183            bucket_slack_bytes: memory.bucket_slack_bytes,
184            payload_bytes: memory.payload_bytes,
185        }
186    }
187}
188
189// Inspect existing TLS only. Heap-only/native callers can have no default
190// runtime; a corrupt or inaccessible runtime must not look like absence.
191pub(super) fn collect_memory_allocations() -> Result<Option<MemoryAllocations>, InternalError> {
192    match ic_memory::default_memory_manager_memory_allocations() {
193        Ok(report) => Ok(Some(report.into())),
194        Err(ic_memory::RuntimeDiagnosticError::NotBootstrapped) => Ok(None),
195        Err(_) => Err(InternalError::store_internal()),
196    }
197}