Skip to main content

ic_memory/
diagnostics.rs

1use crate::{
2    ledger::{AllocationLedger, AllocationRecord, GenerationRecord},
3    physical::CommitStoreDiagnostic,
4    slot::AllocationSlotDescriptor,
5};
6use serde::{Deserialize, Serialize};
7
8///
9/// DiagnosticExport
10///
11/// Read-only machine-readable allocation ledger export.
12#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
13pub struct DiagnosticExport {
14    /// Ledger schema version.
15    pub ledger_schema_version: u32,
16    /// Physical format identifier.
17    pub physical_format_id: u32,
18    /// Current committed generation.
19    pub current_generation: u64,
20    /// Ledger anchor descriptor.
21    pub ledger_anchor: AllocationSlotDescriptor,
22    /// Allocation records.
23    pub records: Vec<DiagnosticRecord>,
24    /// Generation records.
25    pub generations: Vec<DiagnosticGeneration>,
26    /// Optional protected commit recovery diagnostic.
27    pub commit_recovery: Option<CommitStoreDiagnostic>,
28}
29
30impl DiagnosticExport {
31    /// Build a read-only diagnostic export from an allocation ledger.
32    #[must_use]
33    pub fn from_ledger(ledger: &AllocationLedger, ledger_anchor: AllocationSlotDescriptor) -> Self {
34        Self::from_ledger_with_commit_recovery(ledger, ledger_anchor, None)
35    }
36
37    /// Build a read-only diagnostic export with protected commit recovery state.
38    #[must_use]
39    pub fn from_ledger_with_commit_recovery(
40        ledger: &AllocationLedger,
41        ledger_anchor: AllocationSlotDescriptor,
42        commit_recovery: Option<CommitStoreDiagnostic>,
43    ) -> Self {
44        Self {
45            ledger_schema_version: ledger.ledger_schema_version,
46            physical_format_id: ledger.physical_format_id,
47            current_generation: ledger.current_generation,
48            ledger_anchor,
49            records: ledger
50                .allocation_history()
51                .records()
52                .iter()
53                .cloned()
54                .map(|allocation| DiagnosticRecord { allocation })
55                .collect(),
56            generations: ledger
57                .allocation_history()
58                .generations()
59                .iter()
60                .cloned()
61                .map(|generation| DiagnosticGeneration { generation })
62                .collect(),
63            commit_recovery,
64        }
65    }
66}
67
68///
69/// DiagnosticRecord
70///
71/// Read-only diagnostic allocation record.
72#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
73pub struct DiagnosticRecord {
74    /// Allocation record.
75    pub allocation: AllocationRecord,
76}
77
78///
79/// DiagnosticGeneration
80///
81/// Read-only diagnostic generation record.
82#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
83pub struct DiagnosticGeneration {
84    /// Generation record.
85    pub generation: GenerationRecord,
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::{
92        declaration::AllocationDeclaration,
93        ledger::{AllocationHistory, AllocationRecord, AllocationState},
94        physical::{CommitRecoveryError, CommitSlotDiagnostic, CommitStoreDiagnostic},
95        schema::SchemaMetadata,
96    };
97
98    #[test]
99    fn diagnostic_export_copies_ledger_records() {
100        let declaration = AllocationDeclaration::new(
101            "app.users.v1",
102            AllocationSlotDescriptor::memory_manager(100).expect("usable slot"),
103            None,
104            SchemaMetadata::default(),
105        )
106        .expect("declaration");
107        let ledger = AllocationLedger {
108            ledger_schema_version: 1,
109            physical_format_id: 1,
110            current_generation: 3,
111            allocation_history: AllocationHistory::from_parts(
112                vec![AllocationRecord::from_declaration(
113                    3,
114                    declaration,
115                    AllocationState::Active,
116                )],
117                vec![GenerationRecord {
118                    generation: 3,
119                    parent_generation: Some(2),
120                    runtime_fingerprint: Some("wasm:abc123".to_string()),
121                    declaration_count: 1,
122                    committed_at: None,
123                }],
124            ),
125        };
126
127        let export = DiagnosticExport::from_ledger(
128            &ledger,
129            AllocationSlotDescriptor::memory_manager(0).expect("usable slot"),
130        );
131
132        assert_eq!(export.current_generation, 3);
133        assert_eq!(export.records.len(), 1);
134        assert_eq!(export.generations.len(), 1);
135        assert_eq!(
136            export.ledger_anchor,
137            AllocationSlotDescriptor::memory_manager(0).expect("usable slot")
138        );
139        assert_eq!(export.commit_recovery, None);
140    }
141
142    #[test]
143    fn diagnostic_export_can_include_commit_recovery_state() {
144        let ledger = AllocationLedger {
145            ledger_schema_version: 1,
146            physical_format_id: 1,
147            current_generation: 3,
148            allocation_history: AllocationHistory::default(),
149        };
150        let commit_recovery = CommitStoreDiagnostic {
151            slot0: CommitSlotDiagnostic {
152                present: true,
153                generation: Some(3),
154                valid: true,
155            },
156            slot1: CommitSlotDiagnostic {
157                present: false,
158                generation: None,
159                valid: false,
160            },
161            authoritative_generation: Some(3),
162            recovery_error: None,
163        };
164
165        let export = DiagnosticExport::from_ledger_with_commit_recovery(
166            &ledger,
167            AllocationSlotDescriptor::memory_manager(0).expect("usable slot"),
168            Some(commit_recovery),
169        );
170
171        assert_eq!(export.commit_recovery, Some(commit_recovery));
172    }
173
174    #[test]
175    fn diagnostic_export_can_report_recovery_failure() {
176        let ledger = AllocationLedger {
177            ledger_schema_version: 1,
178            physical_format_id: 1,
179            current_generation: 0,
180            allocation_history: AllocationHistory::default(),
181        };
182        let commit_recovery = CommitStoreDiagnostic {
183            slot0: CommitSlotDiagnostic {
184                present: false,
185                generation: None,
186                valid: false,
187            },
188            slot1: CommitSlotDiagnostic {
189                present: false,
190                generation: None,
191                valid: false,
192            },
193            authoritative_generation: None,
194            recovery_error: Some(CommitRecoveryError::NoValidGeneration),
195        };
196
197        let export = DiagnosticExport::from_ledger_with_commit_recovery(
198            &ledger,
199            AllocationSlotDescriptor::memory_manager(0).expect("usable slot"),
200            Some(commit_recovery),
201        );
202
203        assert_eq!(
204            export
205                .commit_recovery
206                .expect("commit recovery")
207                .recovery_error,
208            Some(CommitRecoveryError::NoValidGeneration)
209        );
210    }
211}