1use super::{MemoryRuntime, RuntimeDiagnosticError, policy::NoopPolicy};
2use crate::{
3 AllocationHistory, AllocationLedger, AllocationSlotDescriptor, DiagnosticCheck, DiagnosticCode,
4 DiagnosticDeclaration, DiagnosticExport, DiagnosticFailure, DiagnosticMemorySize,
5 DiagnosticRangeAuthority, DiagnosticStableCell, DiagnosticStableCellStatus, LedgerCommitError,
6 LedgerPayloadEnvelopeError, MemoryRuntimeDoctorReport, StableCellLedgerRecord,
7 physical::CommitStoreDiagnostic, registry::SealedDeclarationSnapshot,
8 slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::decode_stable_cell_ledger_record_from_memory,
9};
10use ic_stable_structures::Memory;
11
12impl<M: Memory> MemoryRuntime<M> {
13 pub fn diagnostic_export(&self) -> Result<DiagnosticExport, RuntimeDiagnosticError> {
15 if !self.is_bootstrapped() {
16 return Err(RuntimeDiagnosticError::NotBootstrapped);
17 }
18 let record = self.ledger_record_from_memory()?;
19 let recovered = record.store().recover()?;
20 let ledger = recovered.ledger();
21 Ok(
22 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
23 ledger,
24 ledger_anchor_descriptor(),
25 Some(record.store().physical().diagnostic()),
26 self.memory_sizes(ledger)?,
27 ),
28 )
29 }
30
31 pub fn commit_recovery_diagnostic(
36 &self,
37 ) -> Result<CommitStoreDiagnostic, RuntimeDiagnosticError> {
38 let record = self.ledger_record_from_memory()?;
39 Ok(record.store().physical().diagnostic())
40 }
41
42 #[must_use]
44 pub fn doctor_report(
45 &self,
46 declarations: &SealedDeclarationSnapshot,
47 ) -> MemoryRuntimeDoctorReport {
48 let stable_cell = self.stable_cell_diagnostic();
49 let commit_recovery = stable_cell
50 .record
51 .as_ref()
52 .map(|record| record.store().physical().diagnostic());
53 let recovered = stable_cell
54 .record
55 .as_ref()
56 .map(|record| record.store().recover());
57 let recovered_for_export = recovered.as_ref().and_then(|result| result.as_ref().ok());
58 let ledger = recovered_for_export.map(|recovered| {
59 DiagnosticExport::from_ledger_with_commit_recovery_and_memory_sizes(
60 recovered.ledger(),
61 ledger_anchor_descriptor(),
62 commit_recovery,
63 self.memory_sizes_lossy(recovered.ledger()),
64 )
65 });
66 let diagnostic_declarations = declarations
67 .registered_declarations()
68 .iter()
69 .map(|registration| {
70 DiagnosticDeclaration::new(
71 registration.authority(),
72 registration.declaration().clone(),
73 )
74 })
75 .collect();
76 let registered_records = declarations
77 .registered_ranges()
78 .iter()
79 .map(|registration| registration.record().clone())
80 .collect();
81 let range_authority = DiagnosticRangeAuthority::new(
82 registered_records,
83 Ok(declarations.range_authority().clone()),
84 );
85 let validation = diagnostic_validation(
86 declarations,
87 stable_cell.record.as_ref(),
88 recovered.as_ref(),
89 );
90
91 MemoryRuntimeDoctorReport {
92 bootstrapped: self.is_bootstrapped(),
93 ledger_anchor: ledger_anchor_descriptor(),
94 stable_cell: stable_cell.diagnostic,
95 commit_recovery,
96 ledger,
97 registered_declarations: diagnostic_declarations,
98 range_authority,
99 validation,
100 }
101 }
102
103 fn memory_sizes(
104 &self,
105 ledger: &AllocationLedger,
106 ) -> Result<Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)>, RuntimeDiagnosticError> {
107 ledger
108 .allocation_history()
109 .records()
110 .iter()
111 .map(|record| {
112 let id = record.slot().memory_manager_id()?;
113 Ok((
114 record.slot().clone(),
115 DiagnosticMemorySize::from_wasm_pages(self.memory(id).size()),
116 ))
117 })
118 .collect()
119 }
120
121 fn memory_sizes_lossy(
122 &self,
123 ledger: &AllocationLedger,
124 ) -> Vec<(AllocationSlotDescriptor, DiagnosticMemorySize)> {
125 self.memory_sizes(ledger).unwrap_or_default()
126 }
127
128 fn stable_cell_diagnostic(&self) -> StableCellDiagnostic {
129 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
130 let memory_size = DiagnosticMemorySize::from_wasm_pages(memory.size());
131 if memory.size() == 0 {
132 return StableCellDiagnostic {
133 diagnostic: DiagnosticStableCell::new(
134 DiagnosticStableCellStatus::Empty,
135 memory_size,
136 ),
137 record: Some(StableCellLedgerRecord::default()),
138 };
139 }
140
141 match decode_stable_cell_ledger_record_from_memory(&memory) {
142 Ok(record) => StableCellDiagnostic {
143 diagnostic: DiagnosticStableCell::new(
144 DiagnosticStableCellStatus::Readable,
145 memory_size,
146 ),
147 record: Some(record),
148 },
149 Err(err) => StableCellDiagnostic {
150 diagnostic: DiagnosticStableCell::new(
151 DiagnosticStableCellStatus::Corrupt {
152 failure: DiagnosticFailure::new(
153 DiagnosticCode::StableCell,
154 err.to_string(),
155 ),
156 },
157 memory_size,
158 ),
159 record: None,
160 },
161 }
162 }
163}
164
165struct StableCellDiagnostic {
166 diagnostic: DiagnosticStableCell,
167 record: Option<StableCellLedgerRecord>,
168}
169
170const fn ledger_anchor_descriptor() -> AllocationSlotDescriptor {
171 AllocationSlotDescriptor::memory_manager_unchecked(MEMORY_MANAGER_LEDGER_ID)
172}
173
174fn diagnostic_validation(
175 declarations: &SealedDeclarationSnapshot,
176 stable_cell_record: Option<&StableCellLedgerRecord>,
177 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
178) -> DiagnosticCheck {
179 let recovered = match diagnostic_validation_ledger(stable_cell_record, recovered) {
180 Ok(recovered) => recovered,
181 Err(failure) => return DiagnosticCheck::not_run(failure.code, failure.message),
182 };
183 let policy = super::policy::RuntimeMemoryManagerPolicy {
184 declarations,
185 custom_policy: &NoopPolicy,
186 };
187 match crate::validate_allocations(
188 &recovered,
189 declarations.allocation_snapshot().clone(),
190 &policy,
191 ) {
192 Ok(_) => DiagnosticCheck::passed(),
193 Err(err) => DiagnosticCheck::failed(DiagnosticCode::AllocationValidation, err.to_string()),
194 }
195}
196
197pub(super) fn diagnostic_validation_ledger(
198 stable_cell_record: Option<&StableCellLedgerRecord>,
199 recovered: Option<&Result<crate::RecoveredLedger, LedgerCommitError>>,
200) -> Result<crate::RecoveredLedger, DiagnosticFailure> {
201 if let Some(Ok(recovered)) = recovered {
202 return Ok(recovered.clone());
203 }
204 if let Some(Err(err)) = recovered {
205 if stable_cell_record.is_some_and(|record| record.store().physical().is_uninitialized()) {
206 return diagnostic_genesis_recovered_ledger();
207 }
208 let code = if matches!(
209 err,
210 LedgerCommitError::PayloadEnvelope(
211 LedgerPayloadEnvelopeError::UnsupportedFormat { .. }
212 )
213 ) {
214 DiagnosticCode::UnsupportedFormat
215 } else {
216 DiagnosticCode::LedgerRecovery
217 };
218 return Err(DiagnosticFailure::new(
219 code,
220 format!("protected ledger recovery: {err}"),
221 ));
222 }
223 if stable_cell_record.is_some() {
224 return diagnostic_genesis_recovered_ledger();
225 }
226 Err(DiagnosticFailure::new(
227 DiagnosticCode::StableCell,
228 "stable-cell ledger record is not readable",
229 ))
230}
231
232fn diagnostic_genesis_recovered_ledger() -> Result<crate::RecoveredLedger, DiagnosticFailure> {
233 AllocationLedger::new(0, AllocationHistory::default())
234 .map(|ledger| crate::RecoveredLedger::from_trusted_parts(ledger, 0))
235 .map_err(|err| {
236 DiagnosticFailure::new(
237 DiagnosticCode::GenesisLedger,
238 format!("genesis ledger: {err}"),
239 )
240 })
241}