1use crate::{
2 constants::WASM_PAGE_SIZE_BYTES,
3 ledger::{AllocationLedger, AllocationRecord},
4 physical::CommitSnapshotDiagnostic,
5 policy::PolicyIdentity,
6};
7use serde::Serialize;
8
9#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
19pub struct DiagnosticExport {
20 pub current_generation: u64,
22 pub records: Vec<DiagnosticRecord>,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
34pub struct DiagnosticRuntimeBinding {
35 pub policy_identity: PolicyIdentity,
37 pub allocation_pool: crate::MemoryAllocationPool,
39}
40
41impl DiagnosticRuntimeBinding {
42 #[must_use]
44 pub const fn new(
45 policy_identity: PolicyIdentity,
46 allocation_pool: crate::MemoryAllocationPool,
47 ) -> Self {
48 Self {
49 policy_identity,
50 allocation_pool,
51 }
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
68pub struct MemoryRuntimeDoctorReport {
69 pub bootstrapped: bool,
71 pub tested_policy_identity: PolicyIdentity,
73 pub established_bootstrap_binding: Option<DiagnosticRuntimeBinding>,
75 pub bootstrap_binding: DiagnosticCheck,
78 pub stable_cell: DiagnosticStableCell,
80 pub commit_recovery: Option<CommitSnapshotDiagnostic>,
82 pub ledger: Option<DiagnosticExport>,
84 pub requests: Vec<crate::MemoryRequest>,
86 pub allocation_pool: crate::MemoryAllocationPool,
88 pub validation: DiagnosticCheck,
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
99pub enum DiagnosticCode {
100 #[serde(rename = "stable_cell")]
102 StableCell,
103 #[serde(rename = "unsupported_format")]
105 UnsupportedFormat,
106 #[serde(rename = "ledger_recovery")]
108 LedgerRecovery,
109 #[serde(rename = "allocation_validation")]
111 AllocationValidation,
112 #[serde(rename = "runtime_binding")]
114 RuntimeBinding,
115}
116
117#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
124pub struct DiagnosticFailure {
125 pub code: DiagnosticCode,
127 pub message: String,
129}
130
131impl DiagnosticFailure {
132 #[must_use]
134 pub fn new(code: DiagnosticCode, message: impl Into<String>) -> Self {
135 Self {
136 code,
137 message: message.into(),
138 }
139 }
140}
141
142#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
149pub struct DiagnosticStableCell {
150 pub status: DiagnosticStableCellStatus,
152 pub memory_size: DiagnosticMemorySize,
154}
155
156impl DiagnosticStableCell {
157 #[must_use]
159 pub const fn new(
160 status: DiagnosticStableCellStatus,
161 memory_size: DiagnosticMemorySize,
162 ) -> Self {
163 Self {
164 status,
165 memory_size,
166 }
167 }
168}
169
170#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
177pub enum DiagnosticStableCellStatus {
178 Empty,
180 Readable,
182 Corrupt {
185 failure: DiagnosticFailure,
187 },
188}
189
190#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
197pub enum DiagnosticCheck {
198 NotRun {
200 code: DiagnosticCode,
202 message: String,
204 },
205 Passed,
207 Failed {
209 code: DiagnosticCode,
211 message: String,
213 },
214}
215
216impl DiagnosticCheck {
217 #[must_use]
219 pub const fn passed() -> Self {
220 Self::Passed
221 }
222
223 #[must_use]
225 pub fn failed(code: DiagnosticCode, message: impl Into<String>) -> Self {
226 Self::Failed {
227 code,
228 message: message.into(),
229 }
230 }
231
232 #[must_use]
234 pub fn not_run(code: DiagnosticCode, message: impl Into<String>) -> Self {
235 Self::NotRun {
236 code,
237 message: message.into(),
238 }
239 }
240}
241
242impl DiagnosticExport {
243 #[must_use]
249 #[cfg(any(test, feature = "test-support"))]
250 pub fn from_ledger(ledger: &AllocationLedger) -> Self {
251 Self::from_records(ledger.current_generation, ledger.records.iter().cloned())
252 }
253
254 pub(crate) fn from_owned_ledger(ledger: AllocationLedger) -> Self {
257 Self::from_records(ledger.current_generation, ledger.records.into_iter())
258 }
259
260 fn from_records(
261 current_generation: u64,
262 records: impl Iterator<Item = AllocationRecord>,
263 ) -> Self {
264 Self {
265 current_generation,
266 records: records
267 .map(|allocation| DiagnosticRecord {
268 allocation,
269 memory_size: None,
270 })
271 .collect(),
272 }
273 }
274}
275
276#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
281pub struct DiagnosticRecord {
282 pub allocation: AllocationRecord,
284 #[serde(skip_serializing_if = "Option::is_none")]
289 pub memory_size: Option<DiagnosticMemorySize>,
290}
291
292#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
299pub struct DiagnosticMemorySize {
300 pub wasm_pages: u64,
302}
303
304impl DiagnosticMemorySize {
305 #[must_use]
307 pub const fn from_wasm_pages(wasm_pages: u64) -> Self {
308 Self { wasm_pages }
309 }
310
311 #[must_use]
313 pub const fn bytes(self) -> u64 {
314 self.wasm_pages.saturating_mul(WASM_PAGE_SIZE_BYTES)
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::{AllocationDeclaration, MemoryManagerSlot, ledger::AllocationRecord};
322
323 #[test]
324 fn diagnostic_export_copies_ledger_records() {
325 let declaration = AllocationDeclaration::new(
326 "app.users.v1",
327 MemoryManagerSlot::new(100).expect("usable slot"),
328 )
329 .expect("declaration");
330 let ledger = AllocationLedger {
331 current_generation: 3,
332 records: vec![AllocationRecord::active(&declaration)],
333 };
334
335 let export = DiagnosticExport::from_ledger(&ledger);
336
337 assert_eq!(export.current_generation, 3);
338 assert_eq!(export.records.len(), 1);
339 assert_eq!(export.records[0].memory_size, None);
340
341 assert_eq!(export, DiagnosticExport::from_owned_ledger(ledger));
342 let wire = serde_json::to_value(&export).expect("diagnostic JSON");
343
344 assert_eq!(
345 wire,
346 serde_json::json!({
347 "current_generation": 3,
348 "records": [{"allocation": {"stable_key": "app.users.v1", "slot": 100, "state": "Active"}}]
349 })
350 );
351 }
352
353 #[test]
354 fn diagnostic_outcome_states_serialize() {
355 let stable_cell = DiagnosticStableCell::new(
356 DiagnosticStableCellStatus::Corrupt {
357 failure: DiagnosticFailure::new(
358 DiagnosticCode::StableCell,
359 "bad stable-cell record",
360 ),
361 },
362 DiagnosticMemorySize::from_wasm_pages(1),
363 );
364 let allocation_pool = crate::MemoryAllocationPool::new(Vec::new(), Vec::new()).unwrap();
365 let check = DiagnosticCheck::failed(
366 DiagnosticCode::AllocationValidation,
367 "duplicate declaration",
368 );
369
370 assert_eq!(
371 serde_json::to_value(DiagnosticCheck::passed()).unwrap(),
372 "Passed"
373 );
374 assert_eq!(
375 serde_json::to_value(check).unwrap(),
376 serde_json::json!({
377 "Failed": {"code": "allocation_validation", "message": "duplicate declaration"}
378 })
379 );
380 assert_eq!(
381 serde_json::to_value(stable_cell).unwrap(),
382 serde_json::json!({
383 "status": {"Corrupt": {"failure": {"code": "stable_cell", "message": "bad stable-cell record"}}},
384 "memory_size": {"wasm_pages": 1}
385 })
386 );
387 assert_eq!(
388 serde_json::to_value(allocation_pool).unwrap(),
389 serde_json::json!({
390 "authorities": [], "excluded_ids": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
391 })
392 );
393 }
394
395 #[test]
396 fn diagnostic_codes_have_stable_wire_names() {
397 let cases = [
398 (DiagnosticCode::StableCell, "stable_cell"),
399 (DiagnosticCode::UnsupportedFormat, "unsupported_format"),
400 (DiagnosticCode::LedgerRecovery, "ledger_recovery"),
401 (
402 DiagnosticCode::AllocationValidation,
403 "allocation_validation",
404 ),
405 (DiagnosticCode::RuntimeBinding, "runtime_binding"),
406 ];
407
408 for (code, expected) in cases {
409 assert_eq!(
410 crate::test_cbor::to_value(code).expect("diagnostic code value"),
411 crate::test_cbor::Value::Text(expected.to_string())
412 );
413 }
414 }
415
416 #[test]
417 fn diagnostic_export_can_include_memory_sizes() {
418 let declaration = AllocationDeclaration::new(
419 "app.users.v1",
420 MemoryManagerSlot::new(100).expect("usable slot"),
421 )
422 .expect("declaration");
423 let ledger = AllocationLedger {
424 current_generation: 3,
425 records: vec![AllocationRecord::active(&declaration)],
426 };
427
428 let mut export = DiagnosticExport::from_ledger(&ledger);
429 export.records[0].memory_size = Some(DiagnosticMemorySize::from_wasm_pages(2));
430
431 assert_eq!(
432 export.records[0].memory_size,
433 Some(DiagnosticMemorySize { wasm_pages: 2 })
434 );
435 let wire = serde_json::to_value(&export).expect("diagnostic JSON");
436 assert_eq!(
437 wire["records"][0]["memory_size"],
438 serde_json::json!({"wasm_pages": 2})
439 );
440 }
441}