1#![allow(non_snake_case)]
19
20use crate::core::DMSCResult;
42use serde::{Deserialize, Serialize};
43use std::collections::HashMap;
44use std::time::{Duration, SystemTime};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
49pub enum DMSCHealthStatus {
50 Healthy,
52 Degraded,
54 Unhealthy,
56 Unknown,
58}
59
60impl DMSCHealthStatus {
61 pub fn is_healthy(&self) -> bool {
63 matches!(self, DMSCHealthStatus::Healthy | DMSCHealthStatus::Degraded)
64 }
65
66 pub fn requires_attention(&self) -> bool {
68 matches!(self, DMSCHealthStatus::Unhealthy)
69 }
70
71 pub fn merge(statuses: &[DMSCHealthStatus]) -> DMSCHealthStatus {
74 if statuses.is_empty() {
75 return DMSCHealthStatus::Unknown;
76 }
77
78 let mut has_unhealthy = false;
79 let mut has_degraded = false;
80 let mut has_unknown = false;
81
82 for status in statuses {
83 match status {
84 DMSCHealthStatus::Unhealthy => has_unhealthy = true,
85 DMSCHealthStatus::Degraded => has_degraded = true,
86 DMSCHealthStatus::Unknown => has_unknown = true,
87 DMSCHealthStatus::Healthy => {}
88 }
89 }
90
91 if has_unhealthy {
92 DMSCHealthStatus::Unhealthy
93 } else if has_degraded {
94 DMSCHealthStatus::Degraded
95 } else if has_unknown {
96 DMSCHealthStatus::Unknown
97 } else {
98 DMSCHealthStatus::Healthy
99 }
100 }
101}
102
103impl std::fmt::Display for DMSCHealthStatus {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 DMSCHealthStatus::Healthy => write!(f, "healthy"),
107 DMSCHealthStatus::Degraded => write!(f, "degraded"),
108 DMSCHealthStatus::Unhealthy => write!(f, "unhealthy"),
109 DMSCHealthStatus::Unknown => write!(f, "unknown"),
110 }
111 }
112}
113
114#[cfg(feature = "pyo3")]
115#[pyo3::prelude::pymethods]
116impl DMSCHealthStatus {
117 fn __str__(&self) -> String {
118 self.to_string()
119 }
120
121 fn __repr__(&self) -> String {
122 format!("DMSCHealthStatus::{}", self)
123 }
124
125 #[staticmethod]
126 fn merge_statuses(statuses: Vec<DMSCHealthStatus>) -> Self {
127 DMSCHealthStatus::merge(&statuses)
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
134pub struct DMSCHealthCheckResult {
135 pub name: String,
137 pub status: DMSCHealthStatus,
139 pub message: Option<String>,
141 pub timestamp: SystemTime,
143 pub duration: Duration,
145}
146
147impl DMSCHealthCheckResult {
148 pub fn healthy(name: String, message: Option<String>) -> Self {
150 Self {
151 name,
152 status: DMSCHealthStatus::Healthy,
153 message,
154 timestamp: SystemTime::now(),
155 duration: Duration::ZERO,
156 }
157 }
158
159 pub fn degraded(name: String, message: Option<String>) -> Self {
161 Self {
162 name,
163 status: DMSCHealthStatus::Degraded,
164 message,
165 timestamp: SystemTime::now(),
166 duration: Duration::ZERO,
167 }
168 }
169
170 pub fn unhealthy(name: String, message: Option<String>) -> Self {
172 Self {
173 name,
174 status: DMSCHealthStatus::Unhealthy,
175 message,
176 timestamp: SystemTime::now(),
177 duration: Duration::ZERO,
178 }
179 }
180
181 pub fn unknown(name: String, message: Option<String>) -> Self {
183 Self {
184 name,
185 status: DMSCHealthStatus::Unknown,
186 message,
187 timestamp: SystemTime::now(),
188 duration: Duration::ZERO,
189 }
190 }
191}
192
193#[cfg(feature = "pyo3")]
194#[pyo3::prelude::pymethods]
195impl DMSCHealthCheckResult {
196 #[new]
197 fn new_py(name: String, status: DMSCHealthStatus, message: Option<String>) -> Self {
198 Self {
199 name,
200 status,
201 message,
202 timestamp: SystemTime::now(),
203 duration: Duration::ZERO,
204 }
205 }
206
207 #[staticmethod]
208 fn create_healthy(name: String, message: Option<String>) -> Self {
209 Self::healthy(name, message)
210 }
211
212 #[staticmethod]
213 fn create_degraded(name: String, message: Option<String>) -> Self {
214 Self::degraded(name, message)
215 }
216
217 #[staticmethod]
218 fn create_unhealthy(name: String, message: Option<String>) -> Self {
219 Self::unhealthy(name, message)
220 }
221
222 #[staticmethod]
223 fn create_unknown(name: String, message: Option<String>) -> Self {
224 Self::unknown(name, message)
225 }
226
227 #[getter]
228 fn name(&self) -> String {
229 self.name.clone()
230 }
231
232 #[getter]
233 fn status(&self) -> DMSCHealthStatus {
234 self.status
235 }
236
237 #[getter]
238 fn message(&self) -> Option<String> {
239 self.message.clone()
240 }
241
242 fn __str__(&self) -> String {
243 format!("{}: {}", self.name, self.status)
244 }
245
246 fn __repr__(&self) -> String {
247 format!("DMSCHealthCheckResult {{ name: {:?}, status: {:?} }}", self.name, self.status)
248 }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
254pub struct DMSCHealthCheckConfig {
255 pub check_interval: Duration,
257 pub timeout: Duration,
259 pub failure_threshold: u32,
261 pub success_threshold: u32,
263 pub enabled: bool,
265}
266
267impl Default for DMSCHealthCheckConfig {
268 fn default() -> Self {
269 Self {
270 check_interval: Duration::from_secs(30),
271 timeout: Duration::from_secs(5),
272 failure_threshold: 3,
273 success_threshold: 2,
274 enabled: true,
275 }
276 }
277}
278
279#[cfg(feature = "pyo3")]
280#[pyo3::prelude::pymethods]
281impl DMSCHealthCheckConfig {
282 #[new]
283 fn new_py(check_interval: u64, timeout: u64, failure_threshold: u32, success_threshold: u32, enabled: bool) -> Self {
284 Self {
285 check_interval: Duration::from_secs(check_interval),
286 timeout: Duration::from_secs(timeout),
287 failure_threshold,
288 success_threshold,
289 enabled,
290 }
291 }
292
293 #[staticmethod]
294 fn default_config() -> Self {
295 Self::default()
296 }
297
298 #[getter]
299 fn check_interval(&self) -> u64 {
300 self.check_interval.as_secs()
301 }
302
303 #[setter]
304 fn set_check_interval(&mut self, value: u64) {
305 self.check_interval = Duration::from_secs(value);
306 }
307
308 #[getter]
309 fn timeout(&self) -> u64 {
310 self.timeout.as_secs()
311 }
312
313 #[setter]
314 fn set_timeout(&mut self, value: u64) {
315 self.timeout = Duration::from_secs(value);
316 }
317
318 #[getter]
319 fn failure_threshold(&self) -> u32 {
320 self.failure_threshold
321 }
322
323 #[getter]
324 fn success_threshold(&self) -> u32 {
325 self.success_threshold
326 }
327
328 #[getter]
329 fn enabled(&self) -> bool {
330 self.enabled
331 }
332
333 fn __repr__(&self) -> String {
334 format!("DMSCHealthCheckConfig {{ check_interval: {}, timeout: {}, failure_threshold: {}, success_threshold: {}, enabled: {} }}",
335 self.check_interval.as_secs(), self.timeout.as_secs(), self.failure_threshold, self.success_threshold, self.enabled)
336 }
337}
338
339#[async_trait::async_trait]
341pub trait HealthCheck: Send + Sync {
342 async fn check(&self) -> DMSCResult<DMSCHealthCheckResult>;
344
345 fn name(&self) -> &str;
347
348 fn config(&self) -> &DMSCHealthCheckConfig;
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
355pub struct DMSCHealthReport {
356 pub overall_status: DMSCHealthStatus,
358 pub components: HashMap<String, DMSCHealthCheckResult>,
360 pub timestamp: SystemTime,
362 pub total_components: usize,
364 pub healthy_count: usize,
366 pub degraded_count: usize,
368 pub unhealthy_count: usize,
370 pub unknown_count: usize,
372}
373
374impl DMSCHealthReport {
375 pub fn new() -> Self {
377 Self {
378 overall_status: DMSCHealthStatus::Unknown,
379 components: HashMap::new(),
380 timestamp: SystemTime::now(),
381 total_components: 0,
382 healthy_count: 0,
383 degraded_count: 0,
384 unhealthy_count: 0,
385 unknown_count: 0,
386 }
387 }
388
389 pub fn add_result(&mut self, result: DMSCHealthCheckResult) {
391 match result.status {
392 DMSCHealthStatus::Healthy => self.healthy_count += 1,
393 DMSCHealthStatus::Degraded => self.degraded_count += 1,
394 DMSCHealthStatus::Unhealthy => self.unhealthy_count += 1,
395 DMSCHealthStatus::Unknown => self.unknown_count += 1,
396 }
397 self.total_components += 1;
398 self.components.insert(result.name.clone(), result);
399 self.update_overall_status();
400 }
401
402 fn update_overall_status(&mut self) {
404 let statuses: Vec<DMSCHealthStatus> = self.components.values().map(|r| r.status).collect();
405 self.overall_status = DMSCHealthStatus::merge(&statuses);
406 }
407}
408
409impl Default for DMSCHealthReport {
410 fn default() -> Self {
411 Self::new()
412 }
413}
414
415#[cfg(feature = "pyo3")]
416#[pyo3::prelude::pymethods]
417impl DMSCHealthReport {
418 #[new]
419 fn new_py() -> Self {
420 Self::new()
421 }
422
423 #[staticmethod]
424 fn create() -> Self {
425 Self::new()
426 }
427
428 #[staticmethod]
429 fn from_results(results: Vec<DMSCHealthCheckResult>) -> Self {
430 let mut report = Self::new();
431 for result in results {
432 report.add_result(result);
433 }
434 report
435 }
436
437 #[getter]
438 fn overall_status(&self) -> DMSCHealthStatus {
439 self.overall_status
440 }
441
442 #[getter]
443 fn total_components(&self) -> usize {
444 self.total_components
445 }
446
447 #[getter]
448 fn healthy_count(&self) -> usize {
449 self.healthy_count
450 }
451
452 #[getter]
453 fn degraded_count(&self) -> usize {
454 self.degraded_count
455 }
456
457 #[getter]
458 fn unhealthy_count(&self) -> usize {
459 self.unhealthy_count
460 }
461
462 #[getter]
463 fn unknown_count(&self) -> usize {
464 self.unknown_count
465 }
466
467 fn __str__(&self) -> String {
468 format!("DMSCHealthReport: {} ({}/{} healthy, {} degraded, {} unhealthy, {} unknown)",
469 self.overall_status, self.healthy_count, self.total_components,
470 self.degraded_count, self.unhealthy_count, self.unknown_count)
471 }
472
473 fn __repr__(&self) -> String {
474 format!("DMSCHealthReport {{ overall_status: {:?}, total_components: {} }}", self.overall_status, self.total_components)
475 }
476}
477
478#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
480pub struct DMSCHealthChecker {
481 checks: Vec<Box<dyn HealthCheck>>,
483 _config: DMSCHealthCheckConfig,
485}
486
487impl DMSCHealthChecker {
488 pub fn new() -> Self {
490 Self {
491 checks: Vec::new(),
492 _config: DMSCHealthCheckConfig::default(),
493 }
494 }
495
496 pub fn with_config(config: DMSCHealthCheckConfig) -> Self {
498 Self {
499 checks: Vec::new(),
500 _config: config,
501 }
502 }
503
504 pub fn register_check(&mut self, check: Box<dyn HealthCheck>) {
506 self.checks.push(check);
507 }
508
509 pub async fn check_all(&self) -> DMSCHealthReport {
511 let mut report = DMSCHealthReport::new();
512
513 for check in &self.checks {
514 if !check.config().enabled {
515 continue;
516 }
517
518 let start_time = SystemTime::now();
519 let result = match tokio::time::timeout(check.config().timeout, check.check()).await {
520 Ok(Ok(result)) => result,
521 Ok(Err(err)) => DMSCHealthCheckResult::unknown(
522 check.name().to_string(),
523 Some(format!("Check failed: {err}")),
524 ),
525 Err(_) => DMSCHealthCheckResult::unknown(
526 check.name().to_string(),
527 Some("Check timed out".to_string()),
528 ),
529 };
530
531 let duration = SystemTime::now()
532 .duration_since(start_time)
533 .unwrap_or(Duration::ZERO);
534
535 let mut result_with_duration = result;
536 result_with_duration.duration = duration;
537 report.add_result(result_with_duration);
538 }
539
540 report
541 }
542
543 pub fn check_count(&self) -> usize {
545 self.checks.len()
546 }
547}
548
549impl Default for DMSCHealthChecker {
550 fn default() -> Self {
551 Self::new()
552 }
553}
554
555