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),
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 {
112                records: vec![AllocationRecord::from_declaration(
113                    3,
114                    declaration,
115                    AllocationState::Active,
116                )],
117                generations: 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 =
128            DiagnosticExport::from_ledger(&ledger, AllocationSlotDescriptor::memory_manager(0));
129
130        assert_eq!(export.current_generation, 3);
131        assert_eq!(export.records.len(), 1);
132        assert_eq!(export.generations.len(), 1);
133        assert_eq!(
134            export.ledger_anchor,
135            AllocationSlotDescriptor::memory_manager(0)
136        );
137        assert_eq!(export.commit_recovery, None);
138    }
139
140    #[test]
141    fn diagnostic_export_can_include_commit_recovery_state() {
142        let ledger = AllocationLedger {
143            ledger_schema_version: 1,
144            physical_format_id: 1,
145            current_generation: 3,
146            allocation_history: AllocationHistory::default(),
147        };
148        let commit_recovery = CommitStoreDiagnostic {
149            slot0: CommitSlotDiagnostic {
150                present: true,
151                generation: Some(3),
152                valid: true,
153            },
154            slot1: CommitSlotDiagnostic {
155                present: false,
156                generation: None,
157                valid: false,
158            },
159            authoritative_generation: Some(3),
160            recovery_error: None,
161        };
162
163        let export = DiagnosticExport::from_ledger_with_commit_recovery(
164            &ledger,
165            AllocationSlotDescriptor::memory_manager(0),
166            Some(commit_recovery),
167        );
168
169        assert_eq!(export.commit_recovery, Some(commit_recovery));
170    }
171
172    #[test]
173    fn diagnostic_export_can_report_recovery_failure() {
174        let ledger = AllocationLedger {
175            ledger_schema_version: 1,
176            physical_format_id: 1,
177            current_generation: 0,
178            allocation_history: AllocationHistory::default(),
179        };
180        let commit_recovery = CommitStoreDiagnostic {
181            slot0: CommitSlotDiagnostic {
182                present: false,
183                generation: None,
184                valid: false,
185            },
186            slot1: CommitSlotDiagnostic {
187                present: false,
188                generation: None,
189                valid: false,
190            },
191            authoritative_generation: None,
192            recovery_error: Some(CommitRecoveryError::NoValidGeneration),
193        };
194
195        let export = DiagnosticExport::from_ledger_with_commit_recovery(
196            &ledger,
197            AllocationSlotDescriptor::memory_manager(0),
198            Some(commit_recovery),
199        );
200
201        assert_eq!(
202            export
203                .commit_recovery
204                .expect("commit recovery")
205                .recovery_error,
206            Some(CommitRecoveryError::NoValidGeneration)
207        );
208    }
209}