pub mod alerts;
pub mod dashboard;
pub mod metrics;
pub use alerts::*;
pub use dashboard::*;
pub use metrics::*;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MetricType {
Counter,
Gauge,
Histogram,
Timer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeResolution {
Second,
Minute,
Hour,
Day,
}
impl TimeResolution {
pub fn duration(&self) -> Duration {
match self {
TimeResolution::Second => Duration::from_secs(1),
TimeResolution::Minute => Duration::from_secs(60),
TimeResolution::Hour => Duration::from_secs(3600),
TimeResolution::Day => Duration::from_secs(86400),
}
}
}
#[derive(Debug, Clone)]
pub struct MetricValue {
pub timestamp: Instant,
pub value: f64,
pub labels: HashMap<String, String>,
}
#[derive(Debug, Clone, Default)]
pub struct MetricStats {
pub count: u64,
pub sum: f64,
pub min: f64,
pub max: f64,
pub mean: f64,
pub variance: f64,
pub std_dev: f64,
pub p50: f64,
pub p95: f64,
pub p99: f64,
}
impl MetricStats {
pub fn from_values(values: &[f64]) -> Self {
if values.is_empty() {
return Self::default();
}
let count = values.len() as u64;
let sum: f64 = values.iter().sum();
let mean = sum / count as f64;
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let min = sorted.first().copied().unwrap_or(0.0);
let max = sorted.last().copied().unwrap_or(0.0);
let variance = if count > 1 {
values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (count - 1) as f64
} else {
0.0
};
let std_dev = variance.sqrt();
let p50 = percentile(&sorted, 50.0);
let p95 = percentile(&sorted, 95.0);
let p99 = percentile(&sorted, 99.0);
MetricStats {
count,
sum,
min,
max,
mean,
variance,
std_dev,
p50,
p95,
p99,
}
}
}
fn percentile(sorted: &[f64], p: f64) -> f64 {
if sorted.is_empty() {
return 0.0;
}
if sorted.len() == 1 {
return sorted[0];
}
let idx = p / 100.0 * (sorted.len() - 1) as f64;
let lower = idx.floor() as usize;
let upper = idx.ceil() as usize;
if lower == upper {
return sorted[lower];
}
let weight = idx - lower as f64;
sorted[lower] * (1.0 - weight) + sorted[upper] * weight
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OperationCategory {
Query,
Write,
Read,
Aggregation,
Join,
Sort,
Filter,
GroupBy,
IO,
Memory,
Other,
}
impl std::fmt::Display for OperationCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperationCategory::Query => write!(f, "query"),
OperationCategory::Write => write!(f, "write"),
OperationCategory::Read => write!(f, "read"),
OperationCategory::Aggregation => write!(f, "aggregation"),
OperationCategory::Join => write!(f, "join"),
OperationCategory::Sort => write!(f, "sort"),
OperationCategory::Filter => write!(f, "filter"),
OperationCategory::GroupBy => write!(f, "groupby"),
OperationCategory::IO => write!(f, "io"),
OperationCategory::Memory => write!(f, "memory"),
OperationCategory::Other => write!(f, "other"),
}
}
}
#[derive(Debug, Clone)]
pub struct OperationRecord {
pub name: String,
pub category: OperationCategory,
pub duration_us: u64,
pub rows_processed: Option<usize>,
pub bytes_processed: Option<usize>,
pub timestamp: Instant,
pub success: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ResourceSnapshot {
pub memory_used: usize,
pub memory_available: usize,
pub cpu_usage: f64,
pub thread_count: usize,
pub open_files: usize,
pub timestamp: SystemTime,
}
impl Default for ResourceSnapshot {
fn default() -> Self {
Self {
memory_used: 0,
memory_available: 0,
cpu_usage: 0.0,
thread_count: 0,
open_files: 0,
timestamp: SystemTime::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct DashboardConfig {
pub enabled: bool,
pub retention_period: Duration,
pub aggregation_interval: Duration,
pub max_metrics: usize,
pub alerting_enabled: bool,
pub sample_rate: f64,
}
impl Default for DashboardConfig {
fn default() -> Self {
DashboardConfig {
enabled: true,
retention_period: Duration::from_secs(3600), aggregation_interval: Duration::from_secs(60), max_metrics: 100_000,
alerting_enabled: true,
sample_rate: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct DashboardSnapshot {
pub timestamp: SystemTime,
pub uptime: Duration,
pub total_operations: u64,
pub ops_per_second: f64,
pub avg_latency_us: f64,
pub p99_latency_us: f64,
pub error_rate: f64,
pub total_rows: u64,
pub rows_per_second: f64,
pub total_bytes: u64,
pub bytes_per_second: f64,
pub category_stats: HashMap<OperationCategory, MetricStats>,
pub resources: ResourceSnapshot,
pub active_alerts: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metric_stats() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let stats = MetricStats::from_values(&values);
assert_eq!(stats.count, 10);
assert_eq!(stats.sum, 55.0);
assert_eq!(stats.min, 1.0);
assert_eq!(stats.max, 10.0);
assert!((stats.mean - 5.5).abs() < 0.01);
}
#[test]
fn test_percentile() {
let sorted = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
assert!((percentile(&sorted, 50.0) - 5.0).abs() < 1.0);
assert!((percentile(&sorted, 90.0) - 9.0).abs() < 1.0);
}
#[test]
fn test_time_resolution() {
assert_eq!(TimeResolution::Second.duration(), Duration::from_secs(1));
assert_eq!(TimeResolution::Minute.duration(), Duration::from_secs(60));
assert_eq!(TimeResolution::Hour.duration(), Duration::from_secs(3600));
}
#[test]
fn test_dashboard_config_default() {
let config = DashboardConfig::default();
assert!(config.enabled);
assert!(config.alerting_enabled);
assert_eq!(config.sample_rate, 1.0);
}
}