Mountain/IPC/Common/
ServiceInfo.rs1use std::{
7 collections::HashMap,
8 time::{Duration, Instant},
9};
10
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub enum ServiceState {
16 Running,
18 Degraded,
20 Stopped,
22 Error,
24 Starting,
26 ShuttingDown,
28}
29
30impl ServiceState {
31 pub fn is_operational(&self) -> bool {
33 matches!(self, ServiceState::Running | ServiceState::Degraded | ServiceState::Starting)
34 }
35}
36
37#[derive(Debug, Clone, Serialize)]
39pub struct ServiceInfo {
40 pub name:String,
42 pub version:String,
44 pub state:ServiceState,
46 #[serde(skip)]
48 pub state_since:Instant,
49 pub uptime:Duration,
51 #[serde(skip)]
53 pub last_heartbeat:Option<Instant>,
54 pub dependencies:Vec<String>,
56 pub performance:ServicePerformance,
58 pub endpoint:Option<ServiceEndpoint>,
60}
61
62#[derive(Debug, Clone, Serialize)]
64pub struct ServicePerformance {
65 pub request_count:u64,
67 pub error_count:u64,
69 pub average_response_time_ms:f64,
71 #[serde(skip)]
74 pub last_updated:Instant,
75}
76
77impl ServicePerformance {
78 pub fn new() -> Self {
80 Self {
81 request_count:0,
82 error_count:0,
83 average_response_time_ms:0.0,
84 last_updated:Instant::now(),
85 }
86 }
87
88 pub fn record_request(&mut self, response_time_ms:f64) {
90 self.request_count += 1;
91
92 if self.average_response_time_ms == 0.0 {
94 self.average_response_time_ms = response_time_ms;
95 } else {
96 self.average_response_time_ms = (self.average_response_time_ms * (self.request_count - 1) as f64
97 + response_time_ms)
98 / self.request_count as f64;
99 }
100
101 self.last_updated = Instant::now();
102 }
103
104 pub fn record_error(&mut self) {
106 self.error_count += 1;
107 self.last_updated = Instant::now();
108 }
109
110 pub fn error_rate(&self) -> f64 {
112 if self.request_count == 0 {
113 return 0.0;
114 }
115 self.error_count as f64 / self.request_count as f64
116 }
117}
118
119impl Default for ServicePerformance {
120 fn default() -> Self { Self::new() }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ServiceEndpoint {
126 pub protocol:String,
128 pub address:String,
130 pub port:u16,
132 pub path:Option<String>,
134}
135
136impl ServiceEndpoint {
137 pub fn new(protocol:impl Into<String>, address:impl Into<String>, port:u16) -> Self {
139 Self { protocol:protocol.into(), address:address.into(), port, path:None }
140 }
141
142 pub fn new_unix(path:impl Into<String>) -> Self {
144 Self {
145 protocol:"unix".to_string(),
146 address:String::new(),
147 port:0,
148 path:Some(path.into()),
149 }
150 }
151}
152
153impl ServiceInfo {
154 pub fn new(name:impl Into<String>, version:impl Into<String>) -> Self {
156 Self {
157 name:name.into(),
158 version:version.into(),
159 state:ServiceState::Starting,
160 state_since:Instant::now(),
161 uptime:Duration::ZERO,
162 last_heartbeat:None,
163 dependencies:Vec::new(),
164 performance:ServicePerformance::new(),
165 endpoint:None,
166 }
167 }
168
169 pub fn update_state(&mut self, new_state:ServiceState) {
171 self.state = new_state;
172 self.state_since = Instant::now();
173 }
174
175 pub fn record_heartbeat(&mut self) {
177 self.last_heartbeat = Some(Instant::now());
178
179 if self.state == ServiceState::Running {
181 self.uptime = self.state_since.elapsed();
182 }
183 }
184
185 pub fn is_healthy(&self) -> bool {
187 if !self.state.is_operational() {
188 return false;
189 }
190
191 if let Some(heartbeat) = self.last_heartbeat {
193 if heartbeat.elapsed() > Duration::from_secs(30) {
194 return false;
195 }
196 }
197
198 if self.performance.error_rate() > 0.1 {
200 return false;
201 }
202
203 true
204 }
205
206 pub fn add_dependency(&mut self, dependency:impl Into<String>) { self.dependencies.push(dependency.into()); }
208}
209
210#[derive(Debug, Clone)]
212pub struct ServiceRegistry {
213 pub services:HashMap<String, ServiceInfo>,
215 pub last_discovery:Instant,
217 pub discovery_interval:Duration,
219}
220
221impl ServiceRegistry {
222 pub fn new(discovery_interval:Duration) -> Self {
224 Self { services:HashMap::new(), last_discovery:Instant::now(), discovery_interval }
225 }
226
227 pub fn register(&mut self, service:ServiceInfo) {
229 self.services.insert(service.name.clone(), service);
230 self.last_discovery = Instant::now();
231 }
232
233 pub fn unregister(&mut self, name:&str) -> Option<ServiceInfo> {
235 self.services.remove(name).map(|service| {
236 self.last_discovery = Instant::now();
237 service
238 })
239 }
240
241 pub fn get(&self, name:&str) -> Option<&ServiceInfo> { self.services.get(name) }
243
244 pub fn get_mut(&mut self, name:&str) -> Option<&mut ServiceInfo> { self.services.get_mut(name) }
246
247 pub fn should_discover(&self) -> bool { self.last_discovery.elapsed() >= self.discovery_interval }
249
250 pub fn healthy_services(&self) -> Vec<&ServiceInfo> {
252 self.services.values().filter(|service| service.is_healthy()).collect()
253 }
254
255 pub fn unhealthy_services(&self) -> Vec<&ServiceInfo> {
257 self.services.values().filter(|service| !service.is_healthy()).collect()
258 }
259
260 pub fn mark_discovery(&mut self) { self.last_discovery = Instant::now(); }
262}
263
264impl Default for ServiceRegistry {
265 fn default() -> Self { Self::new(Duration::from_secs(60)) }
266}