Skip to main content

reddb_server/
health.rs

1//! Health and diagnostics types for RedDB services.
2
3use std::collections::BTreeMap;
4use std::path::Path;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum HealthState {
9    Healthy,
10    Degraded,
11    Unhealthy,
12}
13
14#[derive(Debug, Clone)]
15pub struct HealthIssue {
16    pub component: String,
17    pub message: String,
18}
19
20#[derive(Debug, Clone)]
21pub struct HealthReport {
22    pub state: HealthState,
23    pub issues: Vec<HealthIssue>,
24    pub diagnostics: BTreeMap<String, String>,
25    pub checked_at_unix_ms: u128,
26}
27
28impl HealthReport {
29    pub fn new(state: HealthState) -> Self {
30        Self {
31            state,
32            issues: Vec::new(),
33            diagnostics: BTreeMap::new(),
34            checked_at_unix_ms: SystemTime::now()
35                .duration_since(UNIX_EPOCH)
36                .unwrap_or_default()
37                .as_millis(),
38        }
39    }
40
41    pub fn healthy() -> Self {
42        Self::new(HealthState::Healthy)
43    }
44
45    pub fn degraded(message: impl Into<String>) -> Self {
46        let mut report = Self::new(HealthState::Degraded);
47        report.issues.push(HealthIssue {
48            component: "engine".into(),
49            message: message.into(),
50        });
51        report
52    }
53
54    pub fn unhealthy(message: impl Into<String>) -> Self {
55        let mut report = Self::new(HealthState::Unhealthy);
56        report.issues.push(HealthIssue {
57            component: "engine".into(),
58            message: message.into(),
59        });
60        report
61    }
62
63    pub fn with_diagnostic(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
64        self.diagnostics.insert(key.into(), value.into());
65        self
66    }
67
68    pub fn is_healthy(&self) -> bool {
69        matches!(self.state, HealthState::Healthy)
70    }
71
72    pub fn issue(&mut self, component: impl Into<String>, message: impl Into<String>) {
73        self.issues.push(HealthIssue {
74            component: component.into(),
75            message: message.into(),
76        });
77        self.state = HealthState::Degraded;
78    }
79}
80
81pub fn storage_file_health(path: &Path) -> HealthReport {
82    if !path.exists() {
83        return HealthReport::degraded("database file does not exist");
84    }
85    let mut report = HealthReport::healthy();
86    let meta = match std::fs::metadata(path) {
87        Ok(meta) => meta,
88        Err(err) => {
89            return HealthReport::unhealthy(format!("unable to stat database file: {err}"));
90        }
91    };
92
93    report = report.with_diagnostic("path", path.display().to_string());
94    report = report.with_diagnostic("size_bytes", meta.len().to_string());
95    report
96}
97
98pub trait HealthProvider {
99    fn health(&self) -> HealthReport;
100}