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