1use std::collections::VecDeque;
31use std::time::{Duration, SystemTime};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum SystemStatus {
36 Healthy,
38 Degraded,
40 Unhealthy,
42 Critical,
44}
45
46impl SystemStatus {
47 #[must_use]
49 #[inline]
50 pub const fn color_code(&self) -> &'static str {
51 match self {
52 Self::Healthy => "#22c55e", Self::Degraded => "#f59e0b", Self::Unhealthy => "#ef4444", Self::Critical => "#991b1b", }
57 }
58
59 #[must_use]
61 #[inline]
62 pub const fn label(&self) -> &'static str {
63 match self {
64 Self::Healthy => "Healthy",
65 Self::Degraded => "Degraded",
66 Self::Unhealthy => "Unhealthy",
67 Self::Critical => "Critical",
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct PerformanceSnapshot {
75 pub timestamp: SystemTime,
77 pub system_status: SystemStatus,
79 pub storage_used_bytes: u64,
81 pub storage_total_bytes: u64,
83 pub storage_usage_percent: f64,
85 pub bandwidth_upload_bps: u64,
87 pub bandwidth_download_bps: u64,
89 pub avg_latency_ms: u64,
91 pub p95_latency_ms: u64,
93 pub active_connections: u32,
95 pub requests_served: u64,
97 pub error_count: u64,
99 pub cache_hit_rate: f64,
101 pub active_alerts: u32,
103}
104
105impl Default for PerformanceSnapshot {
106 fn default() -> Self {
107 Self {
108 timestamp: SystemTime::now(),
109 system_status: SystemStatus::Healthy,
110 storage_used_bytes: 0,
111 storage_total_bytes: 0,
112 storage_usage_percent: 0.0,
113 bandwidth_upload_bps: 0,
114 bandwidth_download_bps: 0,
115 avg_latency_ms: 0,
116 p95_latency_ms: 0,
117 active_connections: 0,
118 requests_served: 0,
119 error_count: 0,
120 cache_hit_rate: 0.0,
121 active_alerts: 0,
122 }
123 }
124}
125
126impl PerformanceSnapshot {
127 #[must_use]
129 #[inline]
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 #[must_use]
136 pub fn determine_status(&self) -> SystemStatus {
137 if self.storage_usage_percent > 95.0 || self.error_count > 100 || self.active_alerts > 10 {
139 return SystemStatus::Critical;
140 }
141
142 if self.storage_usage_percent > 90.0 || self.avg_latency_ms > 1000 || self.error_count > 50
144 {
145 return SystemStatus::Unhealthy;
146 }
147
148 if self.storage_usage_percent > 75.0
150 || self.avg_latency_ms > 500
151 || self.error_count > 10
152 || self.cache_hit_rate < 50.0
153 {
154 return SystemStatus::Degraded;
155 }
156
157 SystemStatus::Healthy
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct DataPoint {
164 pub timestamp: SystemTime,
166 pub value: f64,
168}
169
170impl DataPoint {
171 #[must_use]
173 #[inline]
174 pub fn new(value: f64) -> Self {
175 Self {
176 timestamp: SystemTime::now(),
177 value,
178 }
179 }
180
181 #[must_use]
183 #[inline]
184 pub fn age_secs(&self) -> u64 {
185 SystemTime::now()
186 .duration_since(self.timestamp)
187 .unwrap_or_default()
188 .as_secs()
189 }
190}
191
192#[derive(Debug, Clone)]
194pub struct TimeSeries {
195 points: VecDeque<DataPoint>,
197 max_points: usize,
199 max_age_secs: u64,
201}
202
203impl TimeSeries {
204 #[must_use]
206 pub fn new(max_points: usize, max_age_secs: u64) -> Self {
207 Self {
208 points: VecDeque::with_capacity(max_points),
209 max_points,
210 max_age_secs,
211 }
212 }
213
214 pub fn add(&mut self, value: f64) {
216 self.points.push_back(DataPoint::new(value));
217
218 let cutoff = SystemTime::now() - Duration::from_secs(self.max_age_secs);
220 while let Some(point) = self.points.front() {
221 if point.timestamp < cutoff {
222 self.points.pop_front();
223 } else {
224 break;
225 }
226 }
227
228 while self.points.len() > self.max_points {
230 self.points.pop_front();
231 }
232 }
233
234 #[must_use]
236 #[inline]
237 pub fn points(&self) -> &VecDeque<DataPoint> {
238 &self.points
239 }
240
241 #[must_use]
243 pub fn latest(&self) -> Option<f64> {
244 self.points.back().map(|p| p.value)
245 }
246
247 #[must_use]
249 pub fn average(&self) -> Option<f64> {
250 if self.points.is_empty() {
251 return None;
252 }
253
254 let sum: f64 = self.points.iter().map(|p| p.value).sum();
255 Some(sum / self.points.len() as f64)
256 }
257
258 #[must_use]
260 pub fn min(&self) -> Option<f64> {
261 self.points
262 .iter()
263 .map(|p| p.value)
264 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
265 }
266
267 #[must_use]
269 pub fn max(&self) -> Option<f64> {
270 self.points
271 .iter()
272 .map(|p| p.value)
273 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
274 }
275
276 #[must_use]
278 #[inline]
279 pub fn len(&self) -> usize {
280 self.points.len()
281 }
282
283 #[must_use]
285 #[inline]
286 pub fn is_empty(&self) -> bool {
287 self.points.is_empty()
288 }
289}
290
291pub struct DashboardData {
293 current: PerformanceSnapshot,
295 storage_history: TimeSeries,
297 bandwidth_upload_history: TimeSeries,
299 bandwidth_download_history: TimeSeries,
301 latency_history: TimeSeries,
303 error_history: TimeSeries,
305}
306
307impl Default for DashboardData {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313impl DashboardData {
314 #[must_use]
316 pub fn new() -> Self {
317 Self {
319 current: PerformanceSnapshot::new(),
320 storage_history: TimeSeries::new(100, 3600),
321 bandwidth_upload_history: TimeSeries::new(100, 3600),
322 bandwidth_download_history: TimeSeries::new(100, 3600),
323 latency_history: TimeSeries::new(100, 3600),
324 error_history: TimeSeries::new(100, 3600),
325 }
326 }
327
328 pub fn update_storage(&mut self, used_bytes: u64, total_bytes: u64) {
330 self.current.storage_used_bytes = used_bytes;
331 self.current.storage_total_bytes = total_bytes;
332 self.current.storage_usage_percent = if total_bytes > 0 {
333 (used_bytes as f64 / total_bytes as f64) * 100.0
334 } else {
335 0.0
336 };
337
338 self.storage_history.add(self.current.storage_usage_percent);
339 }
340
341 pub fn update_bandwidth(&mut self, upload_bps: u64, download_bps: u64) {
343 self.current.bandwidth_upload_bps = upload_bps;
344 self.current.bandwidth_download_bps = download_bps;
345
346 self.bandwidth_upload_history.add(upload_bps as f64);
347 self.bandwidth_download_history.add(download_bps as f64);
348 }
349
350 pub fn update_latency(&mut self, avg_ms: u64, p95_ms: u64) {
352 self.current.avg_latency_ms = avg_ms;
353 self.current.p95_latency_ms = p95_ms;
354
355 self.latency_history.add(avg_ms as f64);
356 }
357
358 #[inline]
360 pub fn update_connections(&mut self, active: u32) {
361 self.current.active_connections = active;
362 }
363
364 #[inline]
366 pub fn update_requests(&mut self, served: u64) {
367 self.current.requests_served = served;
368 }
369
370 pub fn update_errors(&mut self, count: u64) {
372 self.current.error_count = count;
373 self.error_history.add(count as f64);
374 }
375
376 #[inline]
378 pub fn update_cache(&mut self, hit_rate: f64) {
379 self.current.cache_hit_rate = hit_rate;
380 }
381
382 #[inline]
384 pub fn update_alerts(&mut self, count: u32) {
385 self.current.active_alerts = count;
386 }
387
388 #[must_use]
390 pub fn snapshot(&self) -> PerformanceSnapshot {
391 let mut snapshot = self.current.clone();
392 snapshot.system_status = snapshot.determine_status();
393 snapshot.timestamp = SystemTime::now();
394 snapshot
395 }
396
397 #[must_use]
399 #[inline]
400 pub fn storage_trend(&self) -> &TimeSeries {
401 &self.storage_history
402 }
403
404 #[must_use]
406 #[inline]
407 pub fn bandwidth_upload_trend(&self) -> &TimeSeries {
408 &self.bandwidth_upload_history
409 }
410
411 #[must_use]
413 #[inline]
414 pub fn bandwidth_download_trend(&self) -> &TimeSeries {
415 &self.bandwidth_download_history
416 }
417
418 #[must_use]
420 #[inline]
421 pub fn latency_trend(&self) -> &TimeSeries {
422 &self.latency_history
423 }
424
425 #[must_use]
427 #[inline]
428 pub fn error_trend(&self) -> &TimeSeries {
429 &self.error_history
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn test_system_status_labels() {
439 assert_eq!(SystemStatus::Healthy.label(), "Healthy");
440 assert_eq!(SystemStatus::Degraded.label(), "Degraded");
441 assert_eq!(SystemStatus::Unhealthy.label(), "Unhealthy");
442 assert_eq!(SystemStatus::Critical.label(), "Critical");
443 }
444
445 #[test]
446 fn test_performance_snapshot_status() {
447 let mut snapshot = PerformanceSnapshot::new();
448
449 snapshot.cache_hit_rate = 80.0;
451
452 snapshot.storage_usage_percent = 50.0;
454 snapshot.avg_latency_ms = 100;
455 assert_eq!(snapshot.determine_status(), SystemStatus::Healthy);
456
457 snapshot.storage_usage_percent = 80.0;
459 assert_eq!(snapshot.determine_status(), SystemStatus::Degraded);
460
461 snapshot.storage_usage_percent = 92.0;
463 assert_eq!(snapshot.determine_status(), SystemStatus::Unhealthy);
464
465 snapshot.storage_usage_percent = 96.0;
467 assert_eq!(snapshot.determine_status(), SystemStatus::Critical);
468 }
469
470 #[test]
471 fn test_time_series_basic() {
472 let mut ts = TimeSeries::new(10, 3600);
473 assert!(ts.is_empty());
474
475 ts.add(10.0);
476 ts.add(20.0);
477 ts.add(30.0);
478
479 assert_eq!(ts.len(), 3);
480 assert_eq!(ts.latest(), Some(30.0));
481 assert_eq!(ts.average(), Some(20.0));
482 assert_eq!(ts.min(), Some(10.0));
483 assert_eq!(ts.max(), Some(30.0));
484 }
485
486 #[test]
487 fn test_time_series_capacity() {
488 let mut ts = TimeSeries::new(5, 3600);
489
490 for i in 0..10 {
491 ts.add(i as f64);
492 }
493
494 assert_eq!(ts.len(), 5);
496 assert_eq!(ts.latest(), Some(9.0));
497 }
498
499 #[test]
500 fn test_dashboard_data_storage() {
501 let mut dashboard = DashboardData::new();
502 dashboard.update_storage(5000, 10000);
503
504 let snapshot = dashboard.snapshot();
505 assert_eq!(snapshot.storage_used_bytes, 5000);
506 assert_eq!(snapshot.storage_total_bytes, 10000);
507 assert_eq!(snapshot.storage_usage_percent, 50.0);
508 }
509
510 #[test]
511 fn test_dashboard_data_bandwidth() {
512 let mut dashboard = DashboardData::new();
513 dashboard.update_bandwidth(1000, 2000);
514
515 let snapshot = dashboard.snapshot();
516 assert_eq!(snapshot.bandwidth_upload_bps, 1000);
517 assert_eq!(snapshot.bandwidth_download_bps, 2000);
518 }
519
520 #[test]
521 fn test_dashboard_data_latency() {
522 let mut dashboard = DashboardData::new();
523 dashboard.update_latency(100, 250);
524
525 let snapshot = dashboard.snapshot();
526 assert_eq!(snapshot.avg_latency_ms, 100);
527 assert_eq!(snapshot.p95_latency_ms, 250);
528 }
529
530 #[test]
531 fn test_dashboard_trends() {
532 let mut dashboard = DashboardData::new();
533
534 for i in 1..=5 {
535 dashboard.update_storage(i * 1000, 10000);
536 }
537
538 let trend = dashboard.storage_trend();
539 assert_eq!(trend.len(), 5);
540 assert_eq!(trend.latest(), Some(50.0));
541 }
542
543 #[test]
544 fn test_data_point_age() {
545 let point = DataPoint::new(42.0);
546 std::thread::sleep(std::time::Duration::from_millis(100));
547 assert!(point.age_secs() < 1);
548 }
549}