agentic_payments/system/
health.rs1use crate::error::{Error, Result};
4use crate::system::pool::AgentPool;
5use serde::{Deserialize, Serialize};
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum HealthStatus {
11 Healthy,
13 Degraded,
15 Unhealthy,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct HealthCheck {
22 pub status: HealthStatus,
24 pub last_check: i64,
26 pub duration_ms: u64,
28 pub message: Option<String>,
30}
31
32impl HealthCheck {
33 pub fn healthy(duration_ms: u64) -> Self {
35 Self {
36 status: HealthStatus::Healthy,
37 last_check: chrono::Utc::now().timestamp(),
38 duration_ms,
39 message: None,
40 }
41 }
42
43 pub fn degraded(duration_ms: u64, message: String) -> Self {
45 Self {
46 status: HealthStatus::Degraded,
47 last_check: chrono::Utc::now().timestamp(),
48 duration_ms,
49 message: Some(message),
50 }
51 }
52
53 pub fn unhealthy(duration_ms: u64, message: String) -> Self {
55 Self {
56 status: HealthStatus::Unhealthy,
57 last_check: chrono::Utc::now().timestamp(),
58 duration_ms,
59 message: Some(message),
60 }
61 }
62}
63
64pub struct SystemHealth {
66 status: HealthStatus,
67 last_check: Option<Instant>,
68 check_interval: Duration,
69}
70
71impl SystemHealth {
72 pub fn new() -> Self {
74 Self {
75 status: HealthStatus::Healthy,
76 last_check: None,
77 check_interval: Duration::from_secs(30),
78 }
79 }
80
81 pub fn status(&self) -> HealthStatus {
83 self.status
84 }
85
86 pub fn set_status(&mut self, status: HealthStatus) {
88 self.status = status;
89 }
90
91 pub fn is_check_due(&self) -> bool {
93 match self.last_check {
94 None => true,
95 Some(last) => last.elapsed() >= self.check_interval,
96 }
97 }
98
99 pub async fn check_system(&mut self) -> Result<HealthCheck> {
101 let start = Instant::now();
102
103 self.last_check = Some(start);
105
106 let duration_ms = start.elapsed().as_millis() as u64;
108 Ok(HealthCheck::healthy(duration_ms))
109 }
110
111 pub async fn check_agent_pool(&mut self, pool: &AgentPool) -> Result<HealthCheck> {
113 let start = Instant::now();
114
115 if pool.is_empty() {
116 let duration_ms = start.elapsed().as_millis() as u64;
117 self.status = HealthStatus::Unhealthy;
118 return Ok(HealthCheck::unhealthy(
119 duration_ms,
120 "Agent pool is empty".to_string(),
121 ));
122 }
123
124 if pool.size() < 3 {
126 let duration_ms = start.elapsed().as_millis() as u64;
127 self.status = HealthStatus::Degraded;
128 return Ok(HealthCheck::degraded(
129 duration_ms,
130 format!("Only {} agents available (minimum: 3)", pool.size()),
131 ));
132 }
133
134 if let Err(e) = pool.health_check_all().await {
136 let duration_ms = start.elapsed().as_millis() as u64;
137 self.status = HealthStatus::Degraded;
138 return Ok(HealthCheck::degraded(
139 duration_ms,
140 format!("Some agents failed health check: {}", e),
141 ));
142 }
143
144 let duration_ms = start.elapsed().as_millis() as u64;
145 self.status = HealthStatus::Healthy;
146 Ok(HealthCheck::healthy(duration_ms))
147 }
148}
149
150impl Default for SystemHealth {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_health_status() {
162 let mut health = SystemHealth::new();
163 assert_eq!(health.status(), HealthStatus::Healthy);
164
165 health.set_status(HealthStatus::Degraded);
166 assert_eq!(health.status(), HealthStatus::Degraded);
167 }
168
169 #[test]
170 fn test_health_check_creation() {
171 let check = HealthCheck::healthy(100);
172 assert_eq!(check.status, HealthStatus::Healthy);
173 assert!(check.message.is_none());
174
175 let check = HealthCheck::degraded(100, "Warning".to_string());
176 assert_eq!(check.status, HealthStatus::Degraded);
177 assert!(check.message.is_some());
178 }
179
180 #[tokio::test]
181 async fn test_system_health_check() {
182 let mut health = SystemHealth::new();
183 let result = health.check_system().await.unwrap();
184 assert_eq!(result.status, HealthStatus::Healthy);
185 }
186}