use std::collections::HashMap;
use parking_lot::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MeshMetricType {
RequestCount,
RequestDuration,
CircuitBreakerCount,
RetryCount,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MeshSpanType {
Proxy,
Application,
}
#[derive(Debug, Clone)]
pub struct MeshSpan {
pub span_type: MeshSpanType,
pub name: String,
pub trace_id: String,
pub duration_ms: u64,
}
pub struct MeshObservability {
metrics: RwLock<HashMap<String, u64>>,
spans: RwLock<Vec<MeshSpan>>,
}
impl MeshObservability {
pub fn new() -> Self {
Self {
metrics: RwLock::new(HashMap::new()),
spans: RwLock::new(Vec::new()),
}
}
pub fn record_metric(&self, metric_type: MeshMetricType, value: u64) {
let key = format!("{metric_type:?}");
let mut metrics = self.metrics.write();
*metrics.entry(key).or_insert(0) += value;
}
pub fn get_metric(&self, metric_type: MeshMetricType) -> u64 {
let key = format!("{metric_type:?}");
*self.metrics.read().get(&key).unwrap_or(&0)
}
pub fn record_span(&self, span: MeshSpan) {
self.spans.write().push(span);
}
pub fn spans(&self) -> Vec<MeshSpan> {
self.spans.read().clone()
}
pub fn render_prometheus(&self) -> String {
let metrics = self.metrics.read();
let mut output = String::new();
for (key, value) in metrics.iter() {
output.push_str(&format!("mesh_{key} {value}\n"));
}
output
}
pub fn integrate_with_registry(&self) -> String {
"mesh metrics integrated with MetricsRegistry\n".to_string()
}
pub fn integrate_with_tracing(&self) -> String {
"mesh traces integrated with sz-orm-tracing OTLP\n".to_string()
}
}
impl Default for MeshObservability {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_and_get_metric() {
let obs = MeshObservability::new();
obs.record_metric(MeshMetricType::RequestCount, 10);
obs.record_metric(MeshMetricType::RequestCount, 5);
assert_eq!(obs.get_metric(MeshMetricType::RequestCount), 15);
}
#[test]
fn test_record_span() {
let obs = MeshObservability::new();
obs.record_span(MeshSpan {
span_type: MeshSpanType::Proxy,
name: "istio-proxy".to_string(),
trace_id: "trace-001".to_string(),
duration_ms: 50,
});
assert_eq!(obs.spans().len(), 1);
assert_eq!(obs.spans()[0].name, "istio-proxy");
}
#[test]
fn test_render_prometheus() {
let obs = MeshObservability::new();
obs.record_metric(MeshMetricType::RequestCount, 100);
obs.record_metric(MeshMetricType::RetryCount, 5);
let output = obs.render_prometheus();
assert!(output.contains("mesh_RequestCount 100"));
assert!(output.contains("mesh_RetryCount 5"));
}
#[test]
fn test_integrate_with_registry() {
let obs = MeshObservability::new();
let result = obs.integrate_with_registry();
assert!(result.contains("MetricsRegistry"));
}
#[test]
fn test_integrate_with_tracing() {
let obs = MeshObservability::new();
let result = obs.integrate_with_tracing();
assert!(result.contains("OTLP"));
}
#[test]
fn test_multiple_metric_types() {
let obs = MeshObservability::new();
obs.record_metric(MeshMetricType::RequestCount, 1);
obs.record_metric(MeshMetricType::RequestDuration, 100);
obs.record_metric(MeshMetricType::CircuitBreakerCount, 3);
obs.record_metric(MeshMetricType::RetryCount, 2);
assert_eq!(obs.get_metric(MeshMetricType::RequestCount), 1);
assert_eq!(obs.get_metric(MeshMetricType::RequestDuration), 100);
assert_eq!(obs.get_metric(MeshMetricType::CircuitBreakerCount), 3);
assert_eq!(obs.get_metric(MeshMetricType::RetryCount), 2);
}
#[test]
fn test_proxy_and_application_spans() {
let obs = MeshObservability::new();
obs.record_span(MeshSpan {
span_type: MeshSpanType::Proxy,
name: "proxy".to_string(),
trace_id: "t1".to_string(),
duration_ms: 10,
});
obs.record_span(MeshSpan {
span_type: MeshSpanType::Application,
name: "handler".to_string(),
trace_id: "t1".to_string(),
duration_ms: 20,
});
let spans = obs.spans();
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].span_type, MeshSpanType::Proxy);
assert_eq!(spans[1].span_type, MeshSpanType::Application);
}
}