Skip to main content

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
47	/// Instant is not serializable)
48	#[serde(skip)]
49	pub state_since:Instant,
50	/// Service uptime
51	pub uptime:Duration,
52	/// Last heartbeat timestamp (skipped for serialization as Instant is not
53	/// serializable)
54	#[serde(skip)]
55	pub last_heartbeat:Option<Instant>,
56	/// Services this service depends on
57	pub dependencies:Vec<String>,
58	/// Performance metrics for this service
59	pub performance:ServicePerformance,
60	/// Optional network endpoint
61	pub endpoint:Option<ServiceEndpoint>,
62}
63
64/// Performance metrics for a service
65#[derive(Debug, Clone, Serialize)]
66pub struct ServicePerformance {
67	/// Request count
68	pub request_count:u64,
69	/// Error count
70	pub error_count:u64,
71	/// Average response time in milliseconds
72	pub average_response_time_ms:f64,
73	/// Last updated timestamp (skipped for serialization as Instant is not
74	/// serializable)
75	#[serde(skip)]
76	pub last_updated:Instant,
77}
78
79impl ServicePerformance {
80	/// Create new service performance metrics
81	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	/// Record a request
91	pub fn record_request(&mut self, response_time_ms:f64) {
92		self.request_count += 1;
93
94		// Update average response time
95		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	/// Record an error
107	pub fn record_error(&mut self) {
108		self.error_count += 1;
109		self.last_updated = Instant::now();
110	}
111
112	/// Calculate error rate (0.0 to 1.0)
113	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/// Network endpoint for a service
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ServiceEndpoint {
128	/// Protocol (e.g., "ipc", "tcp", "udp")
129	pub protocol:String,
130	/// Host address
131	pub address:String,
132	/// Port number
133	pub port:u16,
134	/// Path (for Unix domain sockets)
135	pub path:Option<String>,
136}
137
138impl ServiceEndpoint {
139	/// Create a new service endpoint
140	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	/// Create a Unix domain socket endpoint
145	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	/// Create a new service info
157	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	/// Update service state
172	pub fn update_state(&mut self, new_state:ServiceState) {
173		self.state = new_state;
174		self.state_since = Instant::now();
175	}
176
177	/// Record heartbeat
178	pub fn record_heartbeat(&mut self) {
179		self.last_heartbeat = Some(Instant::now());
180
181		// Update uptime if service is running
182		if self.state == ServiceState::Running {
183			self.uptime = self.state_since.elapsed();
184		}
185	}
186
187	/// Check if service is healthy
188	pub fn is_healthy(&self) -> bool {
189		if !self.state.is_operational() {
190			return false;
191		}
192
193		// Check if heartbeat is recent (within 30 seconds)
194		if let Some(heartbeat) = self.last_heartbeat {
195			if heartbeat.elapsed() > Duration::from_secs(30) {
196				return false;
197			}
198		}
199
200		// Check error rate (should be below 10%)
201		if self.performance.error_rate() > 0.1 {
202			return false;
203		}
204
205		true
206	}
207
208	/// Add a dependency
209	pub fn add_dependency(&mut self, dependency:impl Into<String>) { self.dependencies.push(dependency.into()); }
210}
211
212/// Registry of all discovered services
213#[derive(Debug, Clone)]
214pub struct ServiceRegistry {
215	/// Map of service name to service info
216	pub services:HashMap<String, ServiceInfo>,
217	/// Last discovery timestamp (not serializable)
218	pub last_discovery:Instant,
219	/// Configurable discovery interval
220	pub discovery_interval:Duration,
221}
222
223impl ServiceRegistry {
224	/// Create a new service registry
225	pub fn new(discovery_interval:Duration) -> Self {
226		Self { services:HashMap::new(), last_discovery:Instant::now(), discovery_interval }
227	}
228
229	/// Register a service
230	pub fn register(&mut self, service:ServiceInfo) {
231		self.services.insert(service.name.clone(), service);
232		self.last_discovery = Instant::now();
233	}
234
235	/// Unregister a service
236	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	/// Get service info by name
244	pub fn get(&self, name:&str) -> Option<&ServiceInfo> { self.services.get(name) }
245
246	/// Get mutable service info by name
247	pub fn get_mut(&mut self, name:&str) -> Option<&mut ServiceInfo> { self.services.get_mut(name) }
248
249	/// Check if it's time for discovery
250	pub fn should_discover(&self) -> bool { self.last_discovery.elapsed() >= self.discovery_interval }
251
252	/// Get all healthy services
253	pub fn healthy_services(&self) -> Vec<&ServiceInfo> {
254		self.services.values().filter(|service| service.is_healthy()).collect()
255	}
256
257	/// Get all unhealthy services
258	pub fn unhealthy_services(&self) -> Vec<&ServiceInfo> {
259		self.services.values().filter(|service| !service.is_healthy()).collect()
260	}
261
262	/// Mark discovery time
263	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}