nodedb_lite/nodedb/
diagnostic.rs1use serde::Serialize;
15
16use crate::storage::engine::StorageEngine;
17
18use super::core::NodeDbLite;
19use super::health::HealthStatus;
20use super::lock_ext::LockExt;
21
22#[derive(Debug, Serialize)]
24pub struct DiagnosticDump {
25 pub timestamp_ms: u64,
27 pub health: HealthStatus,
29 pub pending_summary: PendingSummary,
31 pub storage_counts: StorageCounts,
33 pub build_info: BuildInfo,
35}
36
37#[derive(Debug, Serialize)]
39pub struct PendingSummary {
40 pub count: usize,
42 pub total_bytes: usize,
44 pub oldest_mutation_id: u64,
46 pub newest_mutation_id: u64,
48 pub collections: Vec<String>,
50}
51
52#[derive(Debug, Serialize)]
54pub struct StorageCounts {
55 pub meta: u64,
56 pub vector: u64,
57 pub graph: u64,
58 pub crdt: u64,
59 pub loro_state: u64,
60}
61
62#[derive(Debug, Serialize)]
64pub struct BuildInfo {
65 pub version: &'static str,
66 pub target: &'static str,
67 pub profile: &'static str,
68}
69
70impl<S: StorageEngine> NodeDbLite<S> {
71 pub async fn diagnostic_dump(&self) -> DiagnosticDump {
76 let health = self.health();
77
78 let pending_summary = {
79 let crdt = self.crdt.lock_or_recover();
80 let pending = crdt.pending_deltas();
81 let total_bytes: usize = pending.iter().map(|d| d.delta_bytes.len()).sum();
82 let oldest = pending.iter().map(|d| d.mutation_id).min().unwrap_or(0);
83 let newest = pending.iter().map(|d| d.mutation_id).max().unwrap_or(0);
84 let mut collections: Vec<String> = pending
85 .iter()
86 .map(|d| d.collection.clone())
87 .collect::<std::collections::HashSet<_>>()
88 .into_iter()
89 .collect();
90 collections.sort();
91
92 PendingSummary {
93 count: pending.len(),
94 total_bytes,
95 oldest_mutation_id: oldest,
96 newest_mutation_id: newest,
97 collections,
98 }
99 };
100
101 let storage_counts = StorageCounts {
102 meta: self
103 .storage
104 .count(nodedb_types::Namespace::Meta)
105 .await
106 .unwrap_or(0),
107 vector: self
108 .storage
109 .count(nodedb_types::Namespace::Vector)
110 .await
111 .unwrap_or(0),
112 graph: self
113 .storage
114 .count(nodedb_types::Namespace::Graph)
115 .await
116 .unwrap_or(0),
117 crdt: self
118 .storage
119 .count(nodedb_types::Namespace::Crdt)
120 .await
121 .unwrap_or(0),
122 loro_state: self
123 .storage
124 .count(nodedb_types::Namespace::LoroState)
125 .await
126 .unwrap_or(0),
127 };
128
129 let build_info = BuildInfo {
130 version: env!("CARGO_PKG_VERSION"),
131 target: std::env::consts::ARCH,
132 profile: if cfg!(debug_assertions) {
133 "debug"
134 } else {
135 "release"
136 },
137 };
138
139 DiagnosticDump {
140 timestamp_ms: crate::runtime::now_millis(),
141 health,
142 pending_summary,
143 storage_counts,
144 build_info,
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::PagedbStorageMem;
153
154 async fn make_db() -> std::sync::Arc<NodeDbLite<PagedbStorageMem>> {
155 let storage = PagedbStorageMem::open_in_memory().await.unwrap();
156 NodeDbLite::open(storage).await.unwrap()
157 }
158
159 #[tokio::test]
160 async fn diagnostic_dump_empty_db() {
161 let db = make_db().await;
162 let dump = db.diagnostic_dump().await;
163
164 assert_eq!(dump.pending_summary.count, 0);
165 assert!(!dump.build_info.version.is_empty());
166 assert!(dump.timestamp_ms > 0);
167
168 let json = sonic_rs::to_string_pretty(&dump).unwrap();
170 assert!(json.contains("\"health\""));
171 assert!(json.contains("\"pending_summary\""));
172 assert!(json.contains("\"storage_counts\""));
173 }
174
175 #[tokio::test]
176 async fn diagnostic_dump_with_pending() {
177 use nodedb_client::NodeDb;
178 use nodedb_types::document::Document;
179
180 let db = make_db().await;
181 let mut doc = Document::new("d1");
182 doc.set("key", nodedb_types::Value::String("value".into()));
183 db.document_put("test_coll", doc).await.unwrap();
184
185 let dump = db.diagnostic_dump().await;
186 assert!(dump.pending_summary.count > 0);
187 assert!(
188 dump.pending_summary
189 .collections
190 .contains(&"test_coll".to_string())
191 );
192 assert!(dump.pending_summary.oldest_mutation_id > 0);
193 }
194}