khive_db/diagnostics/
disk_guard.rs1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
6pub struct DiskGuardDiagnostics {
7 pub enabled: bool,
8 pub effective_reserve_bytes: Option<u64>,
9 pub reserve_source: Option<String>,
10 pub guard_deadline_ms: Option<u64>,
11 pub deadline_source: Option<String>,
12 pub probe_path: Option<String>,
13 pub volume_identity: Option<String>,
14 pub volume_root: Option<String>,
15 pub unavailable_reason: Option<String>,
16}
17
18impl DiskGuardDiagnostics {
19 pub(super) fn snapshot(pool: &ConnectionPool) -> Self {
20 let Some(policy) = pool.effective_disk_guard_config() else {
21 return Self {
22 enabled: false,
23 effective_reserve_bytes: None,
24 reserve_source: None,
25 guard_deadline_ms: None,
26 deadline_source: None,
27 probe_path: None,
28 volume_identity: None,
29 volume_root: None,
30 unavailable_reason: Some(if pool.config().read_only {
31 "read-only backend: no disk-write admission".to_string()
32 } else {
33 "in-memory backend: no filesystem volume to guard".to_string()
34 }),
35 };
36 };
37 let identity = pool.write_admission().volume_identity();
38 let (probe_path, volume_identity, volume_root, unavailable_reason) = match identity {
39 Ok(Some(volume)) => (
40 Some(volume.probe_path().display().to_string()),
41 Some(volume.diagnostic_key()),
42 volume.volume_root().map(|path| path.display().to_string()),
43 None,
44 ),
45 Ok(None) => (
46 None,
47 None,
48 None,
49 Some("writable disk guard has no canonical database path".to_string()),
50 ),
51 Err(error) => (None, None, None, Some(error.to_string())),
52 };
53 Self {
54 enabled: true,
55 effective_reserve_bytes: Some(policy.reserve_bytes),
56 reserve_source: Some(policy.reserve_source.as_str().to_string()),
57 guard_deadline_ms: Some(policy.guard_deadline_ms),
58 deadline_source: Some(policy.deadline_source.as_str().to_string()),
59 probe_path,
60 volume_identity,
61 volume_root,
62 unavailable_reason,
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::disk_guard::VolumeIdentity;
71 use crate::pool::PoolConfig;
72
73 #[test]
74 fn disk_guard_diagnostics_report_effective_policy_and_stable_volume() {
75 let dir = tempfile::tempdir().expect("tempdir");
76 let path = dir.path().join("disk-guard-diagnostics.db");
77 let captured_volume = VolumeIdentity::resolve(&path).expect("initial volume identity");
78 let pool = ConnectionPool::new(PoolConfig {
79 path: Some(path),
80 disk_guard_config: Some(
81 crate::DiskGuardEnvironment::default()
82 .resolve(Some(123), Some(500))
83 .unwrap(),
84 ),
85 write_queue_enabled: Some(false),
86 ..PoolConfig::for_test()
87 })
88 .expect("writable pool");
89 let diagnostics = DiskGuardDiagnostics::snapshot(&pool);
90 assert!(diagnostics.enabled);
91 assert_eq!(diagnostics.effective_reserve_bytes, Some(123));
92 assert_eq!(diagnostics.reserve_source.as_deref(), Some("backend"));
93 assert_eq!(diagnostics.guard_deadline_ms, Some(500));
94 assert_eq!(diagnostics.deadline_source.as_deref(), Some("backend"));
95 assert_eq!(
96 diagnostics.volume_identity,
97 Some(captured_volume.diagnostic_key())
98 );
99 let probe_path = captured_volume.probe_path().display().to_string();
100 assert_eq!(diagnostics.probe_path.as_deref(), Some(probe_path.as_str()));
101 assert!(diagnostics.unavailable_reason.is_none());
102 let json = serde_json::to_value(&diagnostics).unwrap();
103 assert_eq!(json["effective_reserve_bytes"], 123);
104 assert_eq!(json["reserve_source"], "backend");
105 #[cfg(any(unix, windows))]
106 {
107 pool.write_admission()
108 .set_test_current_volume(Some(captured_volume.different_volume_for_test()));
109 let unavailable = DiskGuardDiagnostics::snapshot(&pool);
110 assert!(unavailable.volume_identity.is_none());
111 assert!(unavailable.probe_path.is_none());
112 assert!(unavailable
113 .unavailable_reason
114 .as_deref()
115 .unwrap()
116 .contains("database volume changed"));
117 pool.write_admission().set_test_current_volume(None);
118 assert_eq!(DiskGuardDiagnostics::snapshot(&pool), diagnostics);
119 }
120 }
121}