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)]
49 pub state_since:Instant,
50 pub uptime:Duration,
52 #[serde(skip)]
55 pub last_heartbeat:Option<Instant>,
56 pub dependencies:Vec<String>,
58 pub performance:ServicePerformance,
60 pub endpoint:Option<ServiceEndpoint>,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct ServicePerformance {
67 pub request_count:u64,
69 pub error_count:u64,
71 pub average_response_time_ms:f64,
73 #[serde(skip)]
76 pub last_updated:Instant,
77}
78
79impl ServicePerformance {
80 pub fn new() -> Self {
82 Self {
83 request_count:0,
84 error_count:0,
85 average_response_time_ms:0.0,
86 last_updated:Instant::now(),
87 }
88 }
89
90 pub fn record_request(&mut self, response_time_ms:f64) {
92 self.request_count += 1;
93
94 if self.average_response_time_ms == 0.0 {
96 self.average_response_time_ms = response_time_ms;
97 } else {
98 self.average_response_time_ms = (self.average_response_time_ms * (self.request_count - 1) as f64
99 + response_time_ms)
100 / self.request_count as f64;
101 }
102
103 self.last_updated = Instant::now();
104 }
105
106 pub fn record_error(&mut self) {
108 self.error_count += 1;
109 self.last_updated = Instant::now();
110 }
111
112 pub fn error_rate(&self) -> f64 {
114 if self.request_count == 0 {
115 return 0.0;
116 }
117 self.error_count as f64 / self.request_count as f64
118 }
119}
120
121impl Default for ServicePerformance {
122 fn default() -> Self { Self::new() }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ServiceEndpoint {
128 pub protocol:String,
130 pub address:String,
132 pub port:u16,
134 pub path:Option<String>,
136}
137
138impl ServiceEndpoint {
139 pub fn new(protocol:impl Into<String>, address:impl Into<String>, port:u16) -> Self {
141 Self { protocol:protocol.into(), address:address.into(), port, path:None }
142 }
143
144 pub fn new_unix(path:impl Into<String>) -> Self {
146 Self {
147 protocol:"unix".to_string(),
148 address:String::new(),
149 port:0,
150 path:Some(path.into()),
151 }
152 }
153}
154
155impl ServiceInfo {
156 pub fn new(name:impl Into<String>, version:impl Into<String>) -> Self {
158 Self {
159 name:name.into(),
160 version:version.into(),
161 state:ServiceState::Starting,
162 state_since:Instant::now(),
163 uptime:Duration::ZERO,
164 last_heartbeat:None,
165 dependencies:Vec::new(),
166 performance:ServicePerformance::new(),
167 endpoint:None,
168 }
169 }
170
171 pub fn update_state(&mut self, new_state:ServiceState) {
173 self.state = new_state;
174 self.state_since = Instant::now();
175 }
176
177 pub fn record_heartbeat(&mut self) {
179 self.last_heartbeat = Some(Instant::now());
180
181 if self.state == ServiceState::Running {
183 self.uptime = self.state_since.elapsed();
184 }
185 }
186
187 pub fn is_healthy(&self) -> bool {
189 if !self.state.is_operational() {
190 return false;
191 }
192
193 if let Some(heartbeat) = self.last_heartbeat {
195 if heartbeat.elapsed() > Duration::from_secs(30) {
196 return false;
197 }
198 }
199
200 if self.performance.error_rate() > 0.1 {
202 return false;
203 }
204
205 true
206 }
207
208 pub fn add_dependency(&mut self, dependency:impl Into<String>) { self.dependencies.push(dependency.into()); }
210}
211
212#[derive(Debug, Clone)]
214pub struct ServiceRegistry {
215 pub services:HashMap<String, ServiceInfo>,
217 pub last_discovery:Instant,
219 pub discovery_interval:Duration,
221}
222
223impl ServiceRegistry {
224 pub fn new(discovery_interval:Duration) -> Self {
226 Self { services:HashMap::new(), last_discovery:Instant::now(), discovery_interval }
227 }
228
229 pub fn register(&mut self, service:ServiceInfo) {
231 self.services.insert(service.name.clone(), service);
232 self.last_discovery = Instant::now();
233 }
234
235 pub fn unregister(&mut self, name:&str) -> Option<ServiceInfo> {
237 self.services.remove(name).map(|service| {
238 self.last_discovery = Instant::now();
239 service
240 })
241 }
242
243 pub fn get(&self, name:&str) -> Option<&ServiceInfo> { self.services.get(name) }
245
246 pub fn get_mut(&mut self, name:&str) -> Option<&mut ServiceInfo> { self.services.get_mut(name) }
248
249 pub fn should_discover(&self) -> bool { self.last_discovery.elapsed() >= self.discovery_interval }
251
252 pub fn healthy_services(&self) -> Vec<&ServiceInfo> {
254 self.services.values().filter(|service| service.is_healthy()).collect()
255 }
256
257 pub fn unhealthy_services(&self) -> Vec<&ServiceInfo> {
259 self.services.values().filter(|service| !service.is_healthy()).collect()
260 }
261
262 pub fn mark_discovery(&mut self) { self.last_discovery = Instant::now(); }
264}
265
266impl Default for ServiceRegistry {
267 fn default() -> Self { Self::new(Duration::from_secs(60)) }
268}