use serde::Serialize;
use crate::storage::engine::StorageEngine;
use super::core::NodeDbLite;
use super::health::HealthStatus;
use super::lock_ext::LockExt;
#[derive(Debug, Serialize)]
pub struct DiagnosticDump {
pub timestamp_ms: u64,
pub health: HealthStatus,
pub pending_summary: PendingSummary,
pub storage_counts: StorageCounts,
pub build_info: BuildInfo,
}
#[derive(Debug, Serialize)]
pub struct PendingSummary {
pub count: usize,
pub total_bytes: usize,
pub oldest_mutation_id: u64,
pub newest_mutation_id: u64,
pub collections: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct StorageCounts {
pub meta: u64,
pub vector: u64,
pub graph: u64,
pub crdt: u64,
pub loro_state: u64,
}
#[derive(Debug, Serialize)]
pub struct BuildInfo {
pub version: &'static str,
pub target: &'static str,
pub profile: &'static str,
}
impl<S: StorageEngine> NodeDbLite<S> {
pub async fn diagnostic_dump(&self) -> DiagnosticDump {
let health = self.health();
let pending_summary = {
let crdt = self.crdt.lock_or_recover();
let pending = crdt.pending_deltas();
let total_bytes: usize = pending.iter().map(|d| d.delta_bytes.len()).sum();
let oldest = pending.iter().map(|d| d.mutation_id).min().unwrap_or(0);
let newest = pending.iter().map(|d| d.mutation_id).max().unwrap_or(0);
let mut collections: Vec<String> = pending
.iter()
.map(|d| d.collection.clone())
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
collections.sort();
PendingSummary {
count: pending.len(),
total_bytes,
oldest_mutation_id: oldest,
newest_mutation_id: newest,
collections,
}
};
let storage_counts = StorageCounts {
meta: self
.storage
.count(nodedb_types::Namespace::Meta)
.await
.unwrap_or(0),
vector: self
.storage
.count(nodedb_types::Namespace::Vector)
.await
.unwrap_or(0),
graph: self
.storage
.count(nodedb_types::Namespace::Graph)
.await
.unwrap_or(0),
crdt: self
.storage
.count(nodedb_types::Namespace::Crdt)
.await
.unwrap_or(0),
loro_state: self
.storage
.count(nodedb_types::Namespace::LoroState)
.await
.unwrap_or(0),
};
let build_info = BuildInfo {
version: env!("CARGO_PKG_VERSION"),
target: std::env::consts::ARCH,
profile: if cfg!(debug_assertions) {
"debug"
} else {
"release"
},
};
DiagnosticDump {
timestamp_ms: crate::runtime::now_millis(),
health,
pending_summary,
storage_counts,
build_info,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PagedbStorageMem;
async fn make_db() -> std::sync::Arc<NodeDbLite<PagedbStorageMem>> {
let storage = PagedbStorageMem::open_in_memory().await.unwrap();
NodeDbLite::open(storage).await.unwrap()
}
#[tokio::test]
async fn diagnostic_dump_empty_db() {
let db = make_db().await;
let dump = db.diagnostic_dump().await;
assert_eq!(dump.pending_summary.count, 0);
assert!(!dump.build_info.version.is_empty());
assert!(dump.timestamp_ms > 0);
let json = sonic_rs::to_string_pretty(&dump).unwrap();
assert!(json.contains("\"health\""));
assert!(json.contains("\"pending_summary\""));
assert!(json.contains("\"storage_counts\""));
}
#[tokio::test]
async fn diagnostic_dump_with_pending() {
use nodedb_client::NodeDb;
use nodedb_types::document::Document;
let db = make_db().await;
let mut doc = Document::new("d1");
doc.set("key", nodedb_types::Value::String("value".into()));
db.document_put("test_coll", doc).await.unwrap();
let dump = db.diagnostic_dump().await;
assert!(dump.pending_summary.count > 0);
assert!(
dump.pending_summary
.collections
.contains(&"test_coll".to_string())
);
assert!(dump.pending_summary.oldest_mutation_id > 0);
}
}