linkrs_storage/engine/persistence/
diagnostics.rs1use linkrs_core::{StorageError, StorageResult};
2use linkrs_transaction::wal::Lsn;
3
4use crate::engine::persistence_coordinator::{CheckpointStats, PersistenceState};
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
7pub struct SnapshotStats {
8 pub snapshot_count: usize,
9 pub total_size_bytes: u64,
10 pub latest_snapshot_id: Option<u64>,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct PersistenceDiagnostics {
15 pub state: PersistenceState,
16 pub checkpoint_sequence: u64,
17 pub safe_lsn: Lsn,
18 pub last_checkpoint_error: Option<String>,
19 pub last_snapshot_error: Option<String>,
20 pub temporary_checkpoint_count: usize,
21 pub last_checkpoint_stats: Option<CheckpointStats>,
22 pub catalog_lock_acquisitions: u64,
24 pub catalog_lock_wait_nanos: u64,
26 pub catalog_lock_hold_nanos: u64,
28 pub catalog_lock_contentions: u64,
30 pub catalog_lock_by_operation: Vec<CatalogLockDiagnostic>,
32 pub table_lock_read_acquisitions: u64,
34 pub table_lock_read_wait_nanos: u64,
36 pub table_lock_read_contentions: u64,
38 pub table_lock_write_acquisitions: u64,
40 pub table_lock_write_wait_nanos: u64,
42 pub table_lock_write_contentions: u64,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct CheckpointDiagnostics {
48 pub last_stats: Option<CheckpointStats>,
49 pub pending_checkpoint: bool,
50 pub last_trigger_age_secs: Option<u64>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct CatalogLockDiagnostic {
55 pub operation: String,
56 pub acquisitions: u64,
57 pub wait_nanos: u64,
58 pub hold_nanos: u64,
59 pub contentions: u64,
60}
61
62impl crate::engine::persistence_coordinator::PersistenceCoordinator {
63 pub fn verify_snapshot(&self, snapshot_id: u64) -> StorageResult<bool> {
64 let snapshot_manager = self
65 .snapshot_manager
66 .as_ref()
67 .ok_or_else(|| StorageError::not_supported("Snapshots are not enabled"))?;
68
69 snapshot_manager.verify_snapshot(snapshot_id)
70 }
71
72 pub fn cleanup_old_snapshots(&self) -> StorageResult<usize> {
73 let snapshot_manager = self
74 .snapshot_manager
75 .as_ref()
76 .ok_or_else(|| StorageError::not_supported("Snapshots are not enabled"))?;
77
78 snapshot_manager.cleanup_old_snapshots()
79 }
80
81 pub fn cleanup_old_checkpoints(&self, max_checkpoints: usize) -> StorageResult<usize> {
84 let keep = max_checkpoints.max(1);
85 let current_sequence = self.checkpoint_manager.read().current_seq();
86 let retained_by_snapshot = self
87 .snapshot_manager
88 .as_ref()
89 .map(|manager| manager.retained_checkpoint_sequences())
90 .unwrap_or_default();
91 let mut checkpoints: Vec<(u64, std::path::PathBuf)> =
92 std::fs::read_dir(&self.config.checkpoint_dir)?
93 .filter_map(Result::ok)
94 .filter_map(|entry| {
95 let path = entry.path();
96 let name = path.file_name()?.to_str()?;
97 let sequence = name.strip_prefix("checkpoint_")?.parse::<u64>().ok()?;
98 path.is_dir().then_some((sequence, path))
99 })
100 .collect();
101 checkpoints.sort_by_key(|(sequence, _)| *sequence);
102 let remove_count = checkpoints.len().saturating_sub(keep);
103 let mut removed = 0;
104 for (_sequence, path) in checkpoints.into_iter().filter(|(sequence, _)| {
105 *sequence != current_sequence && !retained_by_snapshot.contains(sequence)
106 }) {
107 if removed >= remove_count {
108 break;
109 }
110 std::fs::remove_dir_all(path)?;
111 removed += 1;
112 }
113 if removed > 0 {
114 super::fs_util::sync_directory(&self.config.checkpoint_dir)?;
115 }
116 Ok(removed)
117 }
118
119 pub fn snapshot_stats(&self) -> SnapshotStats {
120 if let Some(snapshot_manager) = self.snapshot_manager.as_ref() {
121 SnapshotStats {
122 snapshot_count: snapshot_manager.snapshot_count(),
123 total_size_bytes: snapshot_manager.total_snapshot_size(),
124 latest_snapshot_id: snapshot_manager.get_latest_snapshot().map(|info| info.id),
125 }
126 } else {
127 SnapshotStats::default()
128 }
129 }
130
131 pub fn diagnostics(&self) -> PersistenceDiagnostics {
132 let temporary_checkpoint_count = std::fs::read_dir(&self.config.checkpoint_dir)
133 .map(|entries| {
134 entries
135 .filter_map(Result::ok)
136 .filter(|entry| {
137 entry
138 .file_name()
139 .to_str()
140 .is_some_and(|name| name.ends_with(".tmp"))
141 })
142 .count()
143 })
144 .unwrap_or(0);
145 let manifest_safe_lsn = self
146 .manifest_manager
147 .latest_safe_lsn()
148 .map(|lsn| Lsn::new(lsn.get()))
149 .unwrap_or_else(|_| *self.last_checkpoint_lsn.read());
150 PersistenceDiagnostics {
151 state: *self.state.read(),
152 checkpoint_sequence: self.checkpoint_manager.read().current_seq(),
153 safe_lsn: manifest_safe_lsn,
154 last_checkpoint_error: self.last_checkpoint_error.read().clone(),
155 last_snapshot_error: self.last_snapshot_error.read().clone(),
156 temporary_checkpoint_count,
157 last_checkpoint_stats: self.last_checkpoint_stats.read().clone(),
158 catalog_lock_acquisitions: 0,
159 catalog_lock_wait_nanos: 0,
160 catalog_lock_hold_nanos: 0,
161 catalog_lock_contentions: 0,
162 catalog_lock_by_operation: Vec::new(),
163 table_lock_read_acquisitions: 0,
164 table_lock_read_wait_nanos: 0,
165 table_lock_read_contentions: 0,
166 table_lock_write_acquisitions: 0,
167 table_lock_write_wait_nanos: 0,
168 table_lock_write_contentions: 0,
169 }
170 }
171
172 pub fn mark_flushed(&self, lsn: Lsn) {
173 *self.last_flush_lsn.write() = lsn;
174 *self.last_flush_time.write() = std::time::Instant::now();
175 }
176
177 pub fn mark_checkpointed(&self, lsn: Lsn) {
178 *self.last_checkpoint_lsn.write() = lsn;
179 *self.last_checkpoint_time.write() = std::time::Instant::now();
180 *self.last_flush_lsn.write() = lsn;
181 *self.last_flush_time.write() = std::time::Instant::now();
182 }
183
184 pub fn checkpoint_diagnostics(&self, pending_checkpoint: bool) -> CheckpointDiagnostics {
185 CheckpointDiagnostics {
186 last_stats: self.last_checkpoint_stats.read().clone(),
187 pending_checkpoint,
188 last_trigger_age_secs: Some(self.last_checkpoint_time.read().elapsed().as_secs()),
189 }
190 }
191}