use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeWindow {
Second,
Minute,
Hour,
Day,
}
impl TimeWindow {
pub fn duration(&self) -> Duration {
match self {
TimeWindow::Second => Duration::from_secs(1),
TimeWindow::Minute => Duration::from_secs(60),
TimeWindow::Hour => Duration::from_secs(3600),
TimeWindow::Day => Duration::from_secs(86400),
}
}
}
#[derive(Debug, Clone)]
pub struct AggregatorConfig {
pub max_data_points: usize,
pub retention_period: Duration,
pub enable_percentiles: bool,
pub enable_trends: bool,
pub sample_rate: usize,
}
impl Default for AggregatorConfig {
fn default() -> Self {
Self {
max_data_points: 10000,
retention_period: Duration::from_secs(3600), enable_percentiles: true,
enable_trends: true,
sample_rate: 1,
}
}
}
impl AggregatorConfig {
pub fn realtime() -> Self {
Self {
max_data_points: 1000,
retention_period: Duration::from_secs(300), enable_percentiles: true,
enable_trends: false,
sample_rate: 1,
}
}
pub fn longterm() -> Self {
Self {
max_data_points: 50000,
retention_period: Duration::from_secs(86400 * 7), enable_percentiles: false,
enable_trends: true,
sample_rate: 10, }
}
pub fn balanced() -> Self {
Self {
max_data_points: 5000,
retention_period: Duration::from_secs(3600), enable_percentiles: true,
enable_trends: true,
sample_rate: 5,
}
}
}
#[derive(Debug, Clone, Copy)]
struct DataPoint {
value: f64,
timestamp: Instant,
}
#[derive(Debug)]
struct TimeSeries {
data: VecDeque<DataPoint>,
sample_counter: usize,
}
impl TimeSeries {
fn new(capacity: usize) -> Self {
Self {
data: VecDeque::with_capacity(capacity),
sample_counter: 0,
}
}
fn add(&mut self, value: f64, max_points: usize, sample_rate: usize) {
self.sample_counter += 1;
if !self.sample_counter.is_multiple_of(sample_rate) {
return;
}
let point = DataPoint {
value,
timestamp: Instant::now(),
};
self.data.push_back(point);
while self.data.len() > max_points {
self.data.pop_front();
}
}
fn cleanup_old(&mut self, retention: Duration) {
let now = Instant::now();
while let Some(point) = self.data.front() {
if now.duration_since(point.timestamp) > retention {
self.data.pop_front();
} else {
break;
}
}
}
fn get_values_in_window(&self, window: Duration) -> Vec<f64> {
let now = Instant::now();
self.data
.iter()
.filter(|p| now.duration_since(p.timestamp) <= window)
.map(|p| p.value)
.collect()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MetricStatistics {
pub count: usize,
pub min: f64,
pub max: f64,
pub avg: f64,
pub stddev: f64,
pub p50: f64,
pub p95: f64,
pub p99: f64,
pub trend: f64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AggregatedStatistics {
pub bandwidth: MetricStatistics,
pub latency: MetricStatistics,
pub connection_rate: MetricStatistics,
pub query_rate: MetricStatistics,
pub error_rate: MetricStatistics,
}
pub struct MetricsAggregator {
config: AggregatorConfig,
bandwidth: RwLock<TimeSeries>,
latency: RwLock<TimeSeries>,
connections: RwLock<TimeSeries>,
queries: RwLock<TimeSeries>,
errors: RwLock<TimeSeries>,
}
impl MetricsAggregator {
pub fn new(config: AggregatorConfig) -> Self {
let capacity = config.max_data_points;
Self {
config,
bandwidth: RwLock::new(TimeSeries::new(capacity)),
latency: RwLock::new(TimeSeries::new(capacity)),
connections: RwLock::new(TimeSeries::new(capacity)),
queries: RwLock::new(TimeSeries::new(capacity)),
errors: RwLock::new(TimeSeries::new(capacity)),
}
}
pub fn record_bandwidth(&self, bytes: u64) {
let mut series = self.bandwidth.write();
series.add(
bytes as f64,
self.config.max_data_points,
self.config.sample_rate,
);
}
pub fn record_latency(&self, ms: u64) {
let mut series = self.latency.write();
series.add(
ms as f64,
self.config.max_data_points,
self.config.sample_rate,
);
}
pub fn record_connection_event(&self) {
let mut series = self.connections.write();
series.add(1.0, self.config.max_data_points, self.config.sample_rate);
}
pub fn record_query_event(&self) {
let mut series = self.queries.write();
series.add(1.0, self.config.max_data_points, self.config.sample_rate);
}
pub fn record_error_event(&self) {
let mut series = self.errors.write();
series.add(1.0, self.config.max_data_points, self.config.sample_rate);
}
pub fn get_statistics(&self, window: TimeWindow) -> AggregatedStatistics {
let duration = window.duration();
AggregatedStatistics {
bandwidth: self.compute_statistics(&self.bandwidth, duration),
latency: self.compute_statistics(&self.latency, duration),
connection_rate: self.compute_statistics(&self.connections, duration),
query_rate: self.compute_statistics(&self.queries, duration),
error_rate: self.compute_statistics(&self.errors, duration),
}
}
fn compute_statistics(
&self,
series: &RwLock<TimeSeries>,
window: Duration,
) -> MetricStatistics {
let data = series.read();
let values = data.get_values_in_window(window);
if values.is_empty() {
return MetricStatistics::default();
}
let count = values.len();
let sum: f64 = values.iter().sum();
let avg = sum / count as f64;
let min = values.iter().copied().fold(f64::INFINITY, f64::min);
let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let variance: f64 = values.iter().map(|v| (v - avg).powi(2)).sum::<f64>() / count as f64;
let stddev = variance.sqrt();
let (p50, p95, p99) = if self.config.enable_percentiles {
let mut sorted = values.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
(
percentile(&sorted, 0.50),
percentile(&sorted, 0.95),
percentile(&sorted, 0.99),
)
} else {
(avg, max, max)
};
let trend = if self.config.enable_trends {
calculate_trend(&values)
} else {
0.0
};
MetricStatistics {
count,
min,
max,
avg,
stddev,
p50,
p95,
p99,
trend,
}
}
pub fn cleanup(&self) {
let retention = self.config.retention_period;
self.bandwidth.write().cleanup_old(retention);
self.latency.write().cleanup_old(retention);
self.connections.write().cleanup_old(retention);
self.queries.write().cleanup_old(retention);
self.errors.write().cleanup_old(retention);
}
pub fn data_point_count(&self) -> usize {
self.bandwidth.read().data.len()
+ self.latency.read().data.len()
+ self.connections.read().data.len()
+ self.queries.read().data.len()
+ self.errors.read().data.len()
}
pub fn clear(&self) {
self.bandwidth.write().data.clear();
self.latency.write().data.clear();
self.connections.write().data.clear();
self.queries.write().data.clear();
self.errors.write().data.clear();
}
}
fn percentile(sorted_values: &[f64], p: f64) -> f64 {
if sorted_values.is_empty() {
return 0.0;
}
let index = (p * (sorted_values.len() - 1) as f64) as usize;
sorted_values[index]
}
fn calculate_trend(values: &[f64]) -> f64 {
if values.len() < 2 {
return 0.0;
}
let n = values.len() as f64;
let x_mean = (n - 1.0) / 2.0;
let y_mean = values.iter().sum::<f64>() / n;
let mut numerator = 0.0;
let mut denominator = 0.0;
for (i, &y) in values.iter().enumerate() {
let x = i as f64;
numerator += (x - x_mean) * (y - y_mean);
denominator += (x - x_mean).powi(2);
}
if denominator.abs() < 1e-10 {
return 0.0;
}
numerator / denominator
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_presets() {
let realtime = AggregatorConfig::realtime();
assert_eq!(realtime.max_data_points, 1000);
assert!(!realtime.enable_trends);
let longterm = AggregatorConfig::longterm();
assert_eq!(longterm.max_data_points, 50000);
assert!(longterm.enable_trends);
let balanced = AggregatorConfig::balanced();
assert_eq!(balanced.sample_rate, 5);
}
#[test]
fn test_time_window_duration() {
assert_eq!(TimeWindow::Second.duration(), Duration::from_secs(1));
assert_eq!(TimeWindow::Minute.duration(), Duration::from_secs(60));
assert_eq!(TimeWindow::Hour.duration(), Duration::from_secs(3600));
assert_eq!(TimeWindow::Day.duration(), Duration::from_secs(86400));
}
#[test]
fn test_record_bandwidth() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
aggregator.record_bandwidth(1024);
aggregator.record_bandwidth(2048);
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.bandwidth.count, 2);
assert_eq!(stats.bandwidth.min, 1024.0);
assert_eq!(stats.bandwidth.max, 2048.0);
}
#[test]
fn test_record_latency() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
aggregator.record_latency(50);
aggregator.record_latency(100);
aggregator.record_latency(75);
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.latency.count, 3);
assert_eq!(stats.latency.min, 50.0);
assert_eq!(stats.latency.max, 100.0);
assert_eq!(stats.latency.avg, 75.0);
}
#[test]
fn test_connection_events() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
for _ in 0..5 {
aggregator.record_connection_event();
}
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.connection_rate.count, 5);
}
#[test]
fn test_query_events() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
for _ in 0..10 {
aggregator.record_query_event();
}
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.query_rate.count, 10);
}
#[test]
fn test_error_events() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
for _ in 0..3 {
aggregator.record_error_event();
}
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.error_rate.count, 3);
}
#[test]
fn test_percentile_calculation() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
assert_eq!(percentile(&values, 0.50), 5.0);
assert_eq!(percentile(&values, 0.95), 9.0); }
#[test]
fn test_trend_calculation() {
let increasing = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let trend = calculate_trend(&increasing);
assert!(trend > 0.0);
let decreasing = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let trend = calculate_trend(&decreasing);
assert!(trend < 0.0);
let flat = vec![3.0, 3.0, 3.0, 3.0, 3.0];
let trend = calculate_trend(&flat);
assert!(trend.abs() < 0.01);
}
#[test]
fn test_sample_rate() {
let config = AggregatorConfig {
sample_rate: 2, ..Default::default()
};
let aggregator = MetricsAggregator::new(config);
for _ in 0..10 {
aggregator.record_bandwidth(1024);
}
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.bandwidth.count, 5); }
#[test]
fn test_data_point_count() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
aggregator.record_bandwidth(1024);
aggregator.record_latency(50);
aggregator.record_connection_event();
assert_eq!(aggregator.data_point_count(), 3);
}
#[test]
fn test_clear() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
aggregator.record_bandwidth(1024);
aggregator.record_latency(50);
assert!(aggregator.data_point_count() > 0);
aggregator.clear();
assert_eq!(aggregator.data_point_count(), 0);
}
#[test]
fn test_max_data_points() {
let config = AggregatorConfig {
max_data_points: 5,
..Default::default()
};
let aggregator = MetricsAggregator::new(config);
for i in 0..10 {
aggregator.record_bandwidth(i * 100);
}
let count = aggregator.bandwidth.read().data.len();
assert_eq!(count, 5);
}
#[test]
fn test_statistics_with_no_data() {
let config = AggregatorConfig::default();
let aggregator = MetricsAggregator::new(config);
let stats = aggregator.get_statistics(TimeWindow::Minute);
assert_eq!(stats.bandwidth.count, 0);
assert_eq!(stats.bandwidth.avg, 0.0);
}
}