use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetric {
pub operation: String,
pub duration_ms: u64,
pub success: bool,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheck {
pub component: String,
pub status: HealthStatus,
pub latency_ms: u64,
pub details: String,
pub last_checked: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsSnapshot {
pub total_packages: usize,
pub production_ready_count: usize,
pub average_score: f64,
pub receipts_generated: usize,
pub last_receipt_time: Option<String>,
pub response_times: HashMap<String, u64>,
pub error_counts: HashMap<String, usize>,
pub uptime_percent: f64,
}
pub struct ObservabilitySystem {
metrics: Vec<PerformanceMetric>,
health_checks: Vec<HealthCheck>,
start_time: SystemTime,
}
impl ObservabilitySystem {
pub fn new() -> Self {
Self {
metrics: Vec::new(),
health_checks: Vec::new(),
start_time: SystemTime::now(),
}
}
pub fn record_metric(&mut self, operation: String, duration: Duration, success: bool) {
let metric = PerformanceMetric {
operation,
duration_ms: duration.as_millis() as u64,
success,
timestamp: chrono::Utc::now().to_rfc3339(),
};
self.metrics.push(metric);
}
pub fn avg_response_time(&self, operation: &str) -> Option<f64> {
let ops: Vec<_> = self
.metrics
.iter()
.filter(|m| m.operation == operation && m.success)
.collect();
if ops.is_empty() {
None
} else {
let total: u64 = ops.iter().map(|m| m.duration_ms).sum();
Some(total as f64 / ops.len() as f64)
}
}
pub fn success_rate(&self, operation: &str) -> Option<f64> {
let ops: Vec<_> = self
.metrics
.iter()
.filter(|m| m.operation == operation)
.collect();
if ops.is_empty() {
None
} else {
let successful = ops.iter().filter(|m| m.success).count();
Some(successful as f64 / ops.len() as f64 * 100.0)
}
}
pub fn record_health_check(
&mut self, component: String, status: HealthStatus, latency: Duration, details: String,
) {
let check = HealthCheck {
component,
status,
latency_ms: latency.as_millis() as u64,
details,
last_checked: chrono::Utc::now().to_rfc3339(),
};
self.health_checks.push(check);
}
pub fn overall_health(&self) -> HealthStatus {
if self.health_checks.is_empty() {
return HealthStatus::Healthy;
}
if self
.health_checks
.iter()
.any(|c| c.status == HealthStatus::Unhealthy)
{
HealthStatus::Unhealthy
} else if self
.health_checks
.iter()
.any(|c| c.status == HealthStatus::Degraded)
{
HealthStatus::Degraded
} else {
HealthStatus::Healthy
}
}
pub fn uptime_percent(&self) -> f64 {
match self.start_time.elapsed() {
Ok(_elapsed) => {
let total_ops = self.metrics.len() as f64;
if total_ops == 0.0 {
100.0
} else {
let successful = self.metrics.iter().filter(|m| m.success).count() as f64;
(successful / total_ops) * 100.0
}
}
Err(_) => 0.0,
}
}
pub fn generate_report(&self) -> HashMap<String, serde_json::Value> {
let mut report = HashMap::new();
report.insert(
"total_operations".to_string(),
serde_json::json!(self.metrics.len()),
);
report.insert(
"uptime_percent".to_string(),
serde_json::json!(self.uptime_percent()),
);
report.insert(
"system_health".to_string(),
serde_json::json!(format!("{:?}", self.overall_health())),
);
let mut operation_metrics = HashMap::new();
let operations: std::collections::HashSet<_> =
self.metrics.iter().map(|m| m.operation.clone()).collect();
for op in operations {
if let Some(avg_time) = self.avg_response_time(&op) {
if let Some(success_rate) = self.success_rate(&op) {
operation_metrics.insert(
op,
serde_json::json!({
"avg_response_time_ms": avg_time,
"success_rate_percent": success_rate
}),
);
}
}
}
report.insert(
"operations".to_string(),
serde_json::json!(operation_metrics),
);
report
}
}
impl Default for ObservabilitySystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_metric_recording() {
let mut obs = ObservabilitySystem::new();
obs.record_metric("test_op".to_string(), Duration::from_millis(100), true);
assert_eq!(obs.metrics.len(), 1);
assert_eq!(obs.metrics[0].duration_ms, 100);
assert!(obs.metrics[0].success);
}
#[test]
fn test_average_response_time() {
let mut obs = ObservabilitySystem::new();
obs.record_metric("op1".to_string(), Duration::from_millis(100), true);
obs.record_metric("op1".to_string(), Duration::from_millis(200), true);
obs.record_metric("op1".to_string(), Duration::from_millis(300), true);
let avg = obs.avg_response_time("op1");
assert!(avg.is_some());
assert_eq!(avg.unwrap(), 200.0);
}
#[test]
fn test_success_rate() {
let mut obs = ObservabilitySystem::new();
obs.record_metric("op2".to_string(), Duration::from_millis(100), true);
obs.record_metric("op2".to_string(), Duration::from_millis(100), true);
obs.record_metric("op2".to_string(), Duration::from_millis(100), false);
let rate = obs.success_rate("op2");
assert!(rate.is_some());
assert_eq!(rate.unwrap(), 66.66666666666666);
}
#[test]
fn test_health_checks() {
let mut obs = ObservabilitySystem::new();
obs.record_health_check(
"api".to_string(),
HealthStatus::Healthy,
Duration::from_millis(10),
"All healthy".to_string(),
);
assert_eq!(obs.overall_health(), HealthStatus::Healthy);
}
}