use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopicMetrics {
pub topic_id: String,
pub latency_p50_ms: u64,
pub latency_p95_ms: u64,
pub avg_message_bytes: u64,
pub mesh_degree: usize,
pub score_distribution: ScoreDistribution,
pub messages_sent: u64,
pub messages_received: u64,
pub join_events: u64,
pub leave_events: u64,
pub suspicion_events: u64,
pub reconvergence_events: u64,
pub anti_entropy_rounds: u64,
pub items_synced: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreDistribution {
pub min: f64,
pub max: f64,
pub mean: f64,
pub median: f64,
}
pub struct TelemetryCollector {
topic_metrics: HashMap<String, TopicMetrics>,
}
impl TelemetryCollector {
pub fn new() -> Self {
Self {
topic_metrics: HashMap::new(),
}
}
pub fn record_latency(&mut self, _topic_id: &str, latency: Duration) {
let _latency_ms = latency.as_millis() as u64;
}
pub fn record_message_sent(&mut self, topic_id: &str, bytes: usize) {
let metrics = self
.topic_metrics
.entry(topic_id.to_string())
.or_insert_with(|| TopicMetrics::new(topic_id));
metrics.messages_sent += 1;
let total = metrics.avg_message_bytes * (metrics.messages_sent - 1) + bytes as u64;
metrics.avg_message_bytes = total / metrics.messages_sent;
}
pub fn record_message_received(&mut self, topic_id: &str, bytes: usize) {
let metrics = self
.topic_metrics
.entry(topic_id.to_string())
.or_insert_with(|| TopicMetrics::new(topic_id));
metrics.messages_received += 1;
let total = metrics.avg_message_bytes * (metrics.messages_received - 1) + bytes as u64;
metrics.avg_message_bytes = total / metrics.messages_received;
}
pub fn record_join(&mut self, topic_id: &str) {
let metrics = self
.topic_metrics
.entry(topic_id.to_string())
.or_insert_with(|| TopicMetrics::new(topic_id));
metrics.join_events += 1;
metrics.mesh_degree += 1;
}
pub fn record_leave(&mut self, topic_id: &str) {
let metrics = self
.topic_metrics
.entry(topic_id.to_string())
.or_insert_with(|| TopicMetrics::new(topic_id));
metrics.leave_events += 1;
if metrics.mesh_degree > 0 {
metrics.mesh_degree -= 1;
}
}
pub fn get_metrics(&self, topic_id: &str) -> Option<&TopicMetrics> {
self.topic_metrics.get(topic_id)
}
pub fn get_all_metrics(&self) -> &HashMap<String, TopicMetrics> {
&self.topic_metrics
}
}
impl Default for TelemetryCollector {
fn default() -> Self {
Self::new()
}
}
impl TopicMetrics {
fn new(topic_id: &str) -> Self {
Self {
topic_id: topic_id.to_string(),
latency_p50_ms: 0,
latency_p95_ms: 0,
avg_message_bytes: 0,
mesh_degree: 0,
score_distribution: ScoreDistribution {
min: 0.0,
max: 0.0,
mean: 0.0,
median: 0.0,
},
messages_sent: 0,
messages_received: 0,
join_events: 0,
leave_events: 0,
suspicion_events: 0,
reconvergence_events: 0,
anti_entropy_rounds: 0,
items_synced: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_telemetry_collector() {
let mut collector = TelemetryCollector::new();
collector.record_message_sent("topic-1", 100);
collector.record_message_sent("topic-1", 200);
let metrics = collector.get_metrics("topic-1").expect("metrics");
assert_eq!(metrics.messages_sent, 2);
assert_eq!(metrics.avg_message_bytes, 150); }
}