Mountain/IPC/Common/
ServiceInfo.rs

1//! # Service Discovery and Information
2//!
3//! Provides service discovery and information tracking for Mountain services.
4//! Used to monitor and manage registered services.
5
6use std::{
7	collections::HashMap,
8	time::{Duration, Instant},
9};
10
11use serde::{Deserialize, Serialize};
12
13/// State of a service
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15pub enum ServiceState {
16	/// Service is running normally
17	Running,
18	/// Service is degraded but operational
19	Degraded,
20	/// Service is stopped
21	Stopped,
22	/// Service has encountered an error
23	Error,
24	/// Service is starting up
25	Starting,
26	/// Service is shutting down
27	ShuttingDown,
28}
29
30impl ServiceState {
31	/// Check if service is operational
32	pub fn is_operational(&self) -> bool {
33		matches!(self, ServiceState::Running | ServiceState::Degraded | ServiceState::Starting)
34	}
35}
36
37/// Information about a single service
38#[derive(Debug, Clone, Serialize)]
39pub struct ServiceInfo {
40	/// Service name
41	pub name:String,
42	/// Service version
43	pub version:String,
44	/// Current state
45	pub state:ServiceState,
46	/// When the service entered its current state (skipped for serialization as Instant is not serializable)
47	#[serde(skip)]
48	pub state_since:Instant,
49	/// Service uptime
50	pub uptime:Duration,
51	/// Last heartbeat timestamp (skipped for serialization as Instant is not serializable)
52	#[serde(skip)]
53	pub last_heartbeat:Option<Instant>,
54	/// Services this service depends on
55	pub dependencies:Vec<String>,
56	/// Performance metrics for this service
57	pub performance:ServicePerformance,
58	/// Optional network endpoint
59	pub endpoint:Option<ServiceEndpoint>,
60}
61
62/// Performance metrics for a service
63#[derive(Debug, Clone, Serialize)]
64pub struct ServicePerformance {
65	/// Request count
66	pub request_count:u64,
67	/// Error count
68	pub error_count:u64,
69	/// Average response time in milliseconds
70	pub average_response_time_ms:f64,
71	/// Last updated timestamp (skipped for serialization as Instant is not
72	/// serializable)
73	#[serde(skip)]
74	pub last_updated:Instant,
75}
76
77impl ServicePerformance {
78	/// Create new service performance metrics
79	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	/// Record a request
89	pub fn record_request(&mut self, response_time_ms:f64) {
90		self.request_count += 1;
91
92		// Update average response time
93		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	/// Record an error
105	pub fn record_error(&mut self) {
106		self.error_count += 1;
107		self.last_updated = Instant::now();
108	}
109
110	/// Calculate error rate (0.0 to 1.0)
111	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/// Network endpoint for a service
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ServiceEndpoint {
126	/// Protocol (e.g., "ipc", "tcp", "udp")
127	pub protocol:String,
128	/// Host address
129	pub address:String,
130	/// Port number
131	pub port:u16,
132	/// Path (for Unix domain sockets)
133	pub path:Option<String>,
134}
135
136impl ServiceEndpoint {
137	/// Create a new service endpoint
138	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	/// Create a Unix domain socket endpoint
143	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	/// Create a new service info
155	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	/// Update service state
170	pub fn update_state(&mut self, new_state:ServiceState) {
171		self.state = new_state;
172		self.state_since = Instant::now();
173	}
174
175	/// Record heartbeat
176	pub fn record_heartbeat(&mut self) {
177		self.last_heartbeat = Some(Instant::now());
178
179		// Update uptime if service is running
180		if self.state == ServiceState::Running {
181			self.uptime = self.state_since.elapsed();
182		}
183	}
184
185	/// Check if service is healthy
186	pub fn is_healthy(&self) -> bool {
187		if !self.state.is_operational() {
188			return false;
189		}
190
191		// Check if heartbeat is recent (within 30 seconds)
192		if let Some(heartbeat) = self.last_heartbeat {
193			if heartbeat.elapsed() > Duration::from_secs(30) {
194				return false;
195			}
196		}
197
198		// Check error rate (should be below 10%)
199		if self.performance.error_rate() > 0.1 {
200			return false;
201		}
202
203		true
204	}
205
206	/// Add a dependency
207	pub fn add_dependency(&mut self, dependency:impl Into<String>) { self.dependencies.push(dependency.into()); }
208}
209
210/// Registry of all discovered services
211#[derive(Debug, Clone)]
212pub struct ServiceRegistry {
213	/// Map of service name to service info
214	pub services:HashMap<String, ServiceInfo>,
215	/// Last discovery timestamp (not serializable)
216	pub last_discovery:Instant,
217	/// Configurable discovery interval
218	pub discovery_interval:Duration,
219}
220
221impl ServiceRegistry {
222	/// Create a new service registry
223	pub fn new(discovery_interval:Duration) -> Self {
224		Self { services:HashMap::new(), last_discovery:Instant::now(), discovery_interval }
225	}
226
227	/// Register a service
228	pub fn register(&mut self, service:ServiceInfo) {
229		self.services.insert(service.name.clone(), service);
230		self.last_discovery = Instant::now();
231	}
232
233	/// Unregister a service
234	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	/// Get service info by name
242	pub fn get(&self, name:&str) -> Option<&ServiceInfo> { self.services.get(name) }
243
244	/// Get mutable service info by name
245	pub fn get_mut(&mut self, name:&str) -> Option<&mut ServiceInfo> { self.services.get_mut(name) }
246
247	/// Check if it's time for discovery
248	pub fn should_discover(&self) -> bool { self.last_discovery.elapsed() >= self.discovery_interval }
249
250	/// Get all healthy services
251	pub fn healthy_services(&self) -> Vec<&ServiceInfo> {
252		self.services.values().filter(|service| service.is_healthy()).collect()
253	}
254
255	/// Get all unhealthy services
256	pub fn unhealthy_services(&self) -> Vec<&ServiceInfo> {
257		self.services.values().filter(|service| !service.is_healthy()).collect()
258	}
259
260	/// Mark discovery time
261	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}