1046 lines
32 KiB
Rust
1046 lines
32 KiB
Rust
//! Validation metrics collection and performance monitoring
|
|
//!
|
|
//! This module provides comprehensive metrics collection for validation operations
|
|
//! including performance metrics, success rates, error tracking, and system
|
|
//! resource utilization.
|
|
|
|
use std::collections::{HashMap, VecDeque};
|
|
use std::time::Duration;
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Main validation metrics collector
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ValidationMetrics {
|
|
/// Performance metrics
|
|
pub performance: PerformanceMetrics,
|
|
/// Success and failure rates
|
|
pub success_rates: SuccessRateMetrics,
|
|
/// Error tracking metrics
|
|
pub error_metrics: ErrorMetrics,
|
|
/// Resource utilization metrics
|
|
pub resource_metrics: ResourceMetrics,
|
|
/// Throughput metrics
|
|
pub throughput_metrics: ThroughputMetrics,
|
|
/// Quality metrics
|
|
pub quality_metrics: QualityMetrics,
|
|
/// Historical metrics (last N measurements)
|
|
pub historical: HistoricalMetrics,
|
|
/// Metrics metadata
|
|
pub metadata: MetricsMetadata,
|
|
}
|
|
|
|
/// Performance-related metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PerformanceMetrics {
|
|
/// Total validation duration in milliseconds
|
|
pub validation_duration_ms: u64,
|
|
/// Average validation time per record
|
|
pub avg_validation_time_ms: f64,
|
|
/// 50th percentile validation time
|
|
pub p50_validation_time_ms: f64,
|
|
/// 95th percentile validation time
|
|
pub p95_validation_time_ms: f64,
|
|
/// 99th percentile validation time
|
|
pub p99_validation_time_ms: f64,
|
|
/// Maximum validation time observed
|
|
pub max_validation_time_ms: u64,
|
|
/// Minimum validation time observed
|
|
pub min_validation_time_ms: u64,
|
|
/// Number of validation rules applied
|
|
pub rule_count: usize,
|
|
/// Number of records processed
|
|
pub records_processed: usize,
|
|
/// Validation queue depth
|
|
pub queue_depth: usize,
|
|
/// Active validation count
|
|
pub active_validations: usize,
|
|
}
|
|
|
|
/// Success and failure rate metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SuccessRateMetrics {
|
|
/// Total number of validations
|
|
pub total_validations: usize,
|
|
/// Number of successful validations
|
|
pub successful_validations: usize,
|
|
/// Number of failed validations
|
|
pub failed_validations: usize,
|
|
/// Overall success rate (0.0 - 1.0)
|
|
pub success_rate: f64,
|
|
/// Success rate over last hour
|
|
pub hourly_success_rate: f64,
|
|
/// Success rate over last day
|
|
pub daily_success_rate: f64,
|
|
/// Success rate trend (increasing/decreasing)
|
|
pub success_rate_trend: TrendDirection,
|
|
/// First validation timestamp
|
|
pub first_validation: Option<DateTime<Utc>>,
|
|
/// Last validation timestamp
|
|
pub last_validation: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
/// Error tracking metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ErrorMetrics {
|
|
/// Total number of errors
|
|
pub total_errors: usize,
|
|
/// Errors by type
|
|
pub errors_by_type: HashMap<String, usize>,
|
|
/// Errors by severity
|
|
pub errors_by_severity: HashMap<String, usize>,
|
|
/// Recent error rate (errors per minute)
|
|
pub error_rate_per_minute: f64,
|
|
/// Error frequency over time
|
|
pub error_frequency: Vec<ErrorFrequencyPoint>,
|
|
/// Most common error types
|
|
pub top_error_types: Vec<(String, usize)>,
|
|
/// Error resolution time statistics
|
|
pub error_resolution_stats: ErrorResolutionStats,
|
|
}
|
|
|
|
/// Resource utilization metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ResourceMetrics {
|
|
/// CPU utilization percentage
|
|
pub cpu_utilization: f64,
|
|
/// Memory usage in bytes
|
|
pub memory_usage_bytes: usize,
|
|
/// Peak memory usage
|
|
pub peak_memory_bytes: usize,
|
|
/// Memory utilization percentage
|
|
pub memory_utilization: f64,
|
|
/// I/O operations count
|
|
pub io_operations: usize,
|
|
/// Network I/O statistics
|
|
pub network_io: NetworkIoStats,
|
|
/// Disk I/O statistics
|
|
pub disk_io: DiskIoStats,
|
|
/// Thread pool utilization
|
|
pub thread_utilization: ThreadUtilization,
|
|
}
|
|
|
|
/// Throughput metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ThroughputMetrics {
|
|
/// Records processed per second
|
|
pub records_per_second: f64,
|
|
/// Peak throughput achieved
|
|
pub peak_records_per_second: f64,
|
|
/// Average throughput over last hour
|
|
pub hourly_avg_throughput: f64,
|
|
/// Throughput trend
|
|
pub throughput_trend: TrendDirection,
|
|
/// Throughput percentiles
|
|
pub throughput_percentiles: ThroughputPercentiles,
|
|
/// Batch processing statistics
|
|
pub batch_stats: BatchStats,
|
|
/// Streaming processing statistics
|
|
pub streaming_stats: StreamingStats,
|
|
}
|
|
|
|
/// Data quality metrics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QualityMetrics {
|
|
/// Average data quality score
|
|
pub avg_quality_score: f64,
|
|
/// Quality score distribution
|
|
pub quality_distribution: QualityDistribution,
|
|
/// Quality trend over time
|
|
pub quality_trend: TrendDirection,
|
|
/// Quality metrics by dimension
|
|
pub quality_by_dimension: HashMap<String, f64>,
|
|
/// Anomaly detection statistics
|
|
pub anomaly_stats: AnomalyStats,
|
|
/// Schema compliance statistics
|
|
pub schema_compliance: SchemaComplianceStats,
|
|
}
|
|
|
|
/// Historical metrics storage
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HistoricalMetrics {
|
|
/// Maximum number of historical points to keep
|
|
pub max_history_points: usize,
|
|
/// Historical validation times
|
|
pub validation_times: VecDeque<f64>,
|
|
/// Historical success rates
|
|
pub success_rates: VecDeque<SuccessRatePoint>,
|
|
/// Historical throughput measurements
|
|
pub throughput_history: VecDeque<ThroughputPoint>,
|
|
/// Historical resource usage
|
|
pub resource_history: VecDeque<ResourceUsagePoint>,
|
|
/// Historical quality scores
|
|
pub quality_history: VecDeque<QualityPoint>,
|
|
}
|
|
|
|
/// Metadata about metrics collection
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MetricsMetadata {
|
|
/// When metrics collection started
|
|
pub collection_started: DateTime<Utc>,
|
|
/// Last metrics update timestamp
|
|
pub last_updated: DateTime<Utc>,
|
|
/// Metrics collection interval (seconds)
|
|
pub collection_interval_seconds: u64,
|
|
/// Metrics version
|
|
pub version: String,
|
|
/// Collector configuration
|
|
pub config: MetricsConfig,
|
|
}
|
|
|
|
/// Configuration for metrics collection
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MetricsConfig {
|
|
/// Enable detailed metrics collection
|
|
pub detailed_collection: bool,
|
|
/// Maximum history retention
|
|
pub max_history_days: u32,
|
|
/// Enable resource monitoring
|
|
pub monitor_resources: bool,
|
|
/// Enable quality tracking
|
|
pub track_quality: bool,
|
|
/// Metrics aggregation interval
|
|
pub aggregation_interval_seconds: u64,
|
|
}
|
|
|
|
/// Trend direction enumeration
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum TrendDirection {
|
|
/// Increasing trend
|
|
Increasing,
|
|
/// Decreasing trend
|
|
Decreasing,
|
|
/// Stable trend
|
|
Stable,
|
|
/// Volatile/unpredictable trend
|
|
Volatile,
|
|
/// Unknown/insufficient data
|
|
Unknown,
|
|
}
|
|
|
|
/// Error frequency point
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ErrorFrequencyPoint {
|
|
/// Timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Error count
|
|
pub count: usize,
|
|
/// Error types at this point
|
|
pub error_types: Vec<String>,
|
|
}
|
|
|
|
/// Error resolution statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ErrorResolutionStats {
|
|
/// Average time to resolve errors (minutes)
|
|
pub avg_resolution_time_minutes: f64,
|
|
/// Number of unresolved errors
|
|
pub unresolved_errors: usize,
|
|
/// Error escalation rate
|
|
pub escalation_rate: f64,
|
|
/// Recovery success rate
|
|
pub recovery_success_rate: f64,
|
|
}
|
|
|
|
/// Network I/O statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct NetworkIoStats {
|
|
/// Bytes sent
|
|
pub bytes_sent: usize,
|
|
/// Bytes received
|
|
pub bytes_received: usize,
|
|
/// Packets sent
|
|
pub packets_sent: usize,
|
|
/// Packets received
|
|
pub packets_received: usize,
|
|
/// Network latency (milliseconds)
|
|
pub latency_ms: f64,
|
|
}
|
|
|
|
/// Disk I/O statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DiskIoStats {
|
|
/// Bytes read from disk
|
|
pub bytes_read: usize,
|
|
/// Bytes written to disk
|
|
pub bytes_written: usize,
|
|
/// Read operations count
|
|
pub read_operations: usize,
|
|
/// Write operations count
|
|
pub write_operations: usize,
|
|
/// Average I/O wait time
|
|
pub avg_io_wait_ms: f64,
|
|
}
|
|
|
|
/// Thread pool utilization
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ThreadUtilization {
|
|
/// Active threads count
|
|
pub active_threads: usize,
|
|
/// Total thread pool size
|
|
pub total_threads: usize,
|
|
/// Thread utilization percentage
|
|
pub utilization_percentage: f64,
|
|
/// Average task queue length
|
|
pub avg_queue_length: f64,
|
|
}
|
|
|
|
/// Throughput percentiles
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ThroughputPercentiles {
|
|
/// 50th percentile
|
|
pub p50: f64,
|
|
/// 75th percentile
|
|
pub p75: f64,
|
|
/// 90th percentile
|
|
pub p90: f64,
|
|
/// 95th percentile
|
|
pub p95: f64,
|
|
/// 99th percentile
|
|
pub p99: f64,
|
|
}
|
|
|
|
/// Batch processing statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BatchStats {
|
|
/// Total batches processed
|
|
pub total_batches: usize,
|
|
/// Average batch size
|
|
pub avg_batch_size: f64,
|
|
/// Average batch processing time
|
|
pub avg_batch_time_ms: f64,
|
|
/// Batch success rate
|
|
pub batch_success_rate: f64,
|
|
}
|
|
|
|
/// Streaming processing statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StreamingStats {
|
|
/// Current stream throughput
|
|
pub current_throughput: f64,
|
|
/// Stream backlog size
|
|
pub backlog_size: usize,
|
|
/// Stream latency (milliseconds)
|
|
pub stream_latency_ms: f64,
|
|
/// Buffer utilization
|
|
pub buffer_utilization: f64,
|
|
}
|
|
|
|
/// Quality score distribution
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QualityDistribution {
|
|
/// Excellent quality (>90%)
|
|
pub excellent_count: usize,
|
|
/// Good quality (70-90%)
|
|
pub good_count: usize,
|
|
/// Fair quality (50-70%)
|
|
pub fair_count: usize,
|
|
/// Poor quality (<50%)
|
|
pub poor_count: usize,
|
|
}
|
|
|
|
/// Anomaly detection statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AnomalyStats {
|
|
/// Total anomalies detected
|
|
pub total_anomalies: usize,
|
|
/// Anomalies by type
|
|
pub anomalies_by_type: HashMap<String, usize>,
|
|
/// Anomaly detection rate
|
|
pub detection_rate: f64,
|
|
/// False positive rate
|
|
pub false_positive_rate: f64,
|
|
}
|
|
|
|
/// Schema compliance statistics
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SchemaComplianceStats {
|
|
/// Overall compliance rate
|
|
pub compliance_rate: f64,
|
|
/// Schema violations count
|
|
pub violations_count: usize,
|
|
/// Compliance by field
|
|
pub field_compliance: HashMap<String, f64>,
|
|
}
|
|
|
|
/// Success rate measurement point
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SuccessRatePoint {
|
|
/// Measurement timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Success rate at this point
|
|
pub rate: f64,
|
|
/// Sample size
|
|
pub sample_size: usize,
|
|
}
|
|
|
|
/// Throughput measurement point
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ThroughputPoint {
|
|
/// Measurement timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Throughput value
|
|
pub throughput: f64,
|
|
/// Measurement duration
|
|
pub duration_seconds: f64,
|
|
}
|
|
|
|
/// Resource usage measurement point
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ResourceUsagePoint {
|
|
/// Measurement timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
/// CPU usage percentage
|
|
pub cpu_percent: f64,
|
|
/// Memory usage in bytes
|
|
pub memory_bytes: usize,
|
|
/// I/O operations count
|
|
pub io_ops: usize,
|
|
}
|
|
|
|
/// Quality measurement point
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QualityPoint {
|
|
/// Measurement timestamp
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Quality score
|
|
pub score: f64,
|
|
/// Number of records in sample
|
|
pub sample_size: usize,
|
|
}
|
|
|
|
impl Default for ValidationMetrics {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl ValidationMetrics {
|
|
/// Create a new metrics collector
|
|
pub fn new() -> Self {
|
|
let now = Utc::now();
|
|
|
|
Self {
|
|
performance: PerformanceMetrics::default(),
|
|
success_rates: SuccessRateMetrics::default(),
|
|
error_metrics: ErrorMetrics::default(),
|
|
resource_metrics: ResourceMetrics::default(),
|
|
throughput_metrics: ThroughputMetrics::default(),
|
|
quality_metrics: QualityMetrics::default(),
|
|
historical: HistoricalMetrics::new(1000), // Keep last 1000 points
|
|
metadata: MetricsMetadata {
|
|
collection_started: now,
|
|
last_updated: now,
|
|
collection_interval_seconds: 60,
|
|
version: "1.0.0".to_string(),
|
|
config: MetricsConfig::default(),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Record a validation operation
|
|
pub fn record_validation(&mut self, duration: Duration, success: bool) {
|
|
let duration_ms = duration.as_millis() as f64;
|
|
|
|
// Update performance metrics
|
|
self.performance.records_processed += 1;
|
|
self.performance.validation_duration_ms += duration.as_millis() as u64;
|
|
|
|
// Update running averages
|
|
let total_records = self.performance.records_processed as f64;
|
|
let prev_avg = self.performance.avg_validation_time_ms;
|
|
self.performance.avg_validation_time_ms =
|
|
prev_avg + (duration_ms - prev_avg) / total_records;
|
|
|
|
// Update min/max
|
|
if self.performance.records_processed == 1 {
|
|
self.performance.min_validation_time_ms = duration.as_millis() as u64;
|
|
self.performance.max_validation_time_ms = duration.as_millis() as u64;
|
|
} else {
|
|
self.performance.min_validation_time_ms = self
|
|
.performance
|
|
.min_validation_time_ms
|
|
.min(duration.as_millis() as u64);
|
|
self.performance.max_validation_time_ms = self
|
|
.performance
|
|
.max_validation_time_ms
|
|
.max(duration.as_millis() as u64);
|
|
}
|
|
|
|
// Update success rates
|
|
self.success_rates.total_validations += 1;
|
|
if success {
|
|
self.success_rates.successful_validations += 1;
|
|
} else {
|
|
self.success_rates.failed_validations += 1;
|
|
}
|
|
|
|
self.success_rates.success_rate = self.success_rates.successful_validations as f64
|
|
/ self.success_rates.total_validations as f64;
|
|
|
|
// Update timestamps
|
|
let now = Utc::now();
|
|
if self.success_rates.first_validation.is_none() {
|
|
self.success_rates.first_validation = Some(now);
|
|
}
|
|
self.success_rates.last_validation = Some(now);
|
|
|
|
// Update historical data
|
|
if self.historical.validation_times.len() >= self.historical.max_history_points {
|
|
self.historical.validation_times.pop_front();
|
|
}
|
|
self.historical.validation_times.push_back(duration_ms);
|
|
|
|
// Update metadata
|
|
self.metadata.last_updated = now;
|
|
|
|
// Calculate percentiles if we have enough data
|
|
if self.historical.validation_times.len() >= 10 {
|
|
self.calculate_percentiles();
|
|
}
|
|
}
|
|
|
|
/// Record an error
|
|
pub fn record_error(&mut self, error_type: &str, severity: &str) {
|
|
self.error_metrics.total_errors += 1;
|
|
*self
|
|
.error_metrics
|
|
.errors_by_type
|
|
.entry(error_type.to_string())
|
|
.or_insert(0) += 1;
|
|
*self
|
|
.error_metrics
|
|
.errors_by_severity
|
|
.entry(severity.to_string())
|
|
.or_insert(0) += 1;
|
|
|
|
// Update error frequency
|
|
let now = Utc::now();
|
|
self.error_metrics
|
|
.error_frequency
|
|
.push(ErrorFrequencyPoint {
|
|
timestamp: now,
|
|
count: 1,
|
|
error_types: vec![error_type.to_string()],
|
|
});
|
|
|
|
// Keep only recent error frequency data (last 1000 points)
|
|
if self.error_metrics.error_frequency.len() > 1000 {
|
|
self.error_metrics.error_frequency.remove(0);
|
|
}
|
|
|
|
// Calculate error rate (simplified)
|
|
let recent_errors = self
|
|
.error_metrics
|
|
.error_frequency
|
|
.iter()
|
|
.filter(|e| (now - e.timestamp).num_minutes() <= 1)
|
|
.map(|e| e.count)
|
|
.sum::<usize>();
|
|
|
|
self.error_metrics.error_rate_per_minute = recent_errors as f64;
|
|
|
|
// Update top error types
|
|
let mut error_types: Vec<_> = self
|
|
.error_metrics
|
|
.errors_by_type
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), *v))
|
|
.collect();
|
|
error_types.sort_by(|a, b| b.1.cmp(&a.1));
|
|
self.error_metrics.top_error_types = error_types.into_iter().take(10).collect();
|
|
}
|
|
|
|
/// Update resource metrics
|
|
pub fn update_resource_metrics(
|
|
&mut self,
|
|
cpu_percent: f64,
|
|
memory_bytes: usize,
|
|
io_ops: usize,
|
|
) {
|
|
self.resource_metrics.cpu_utilization = cpu_percent;
|
|
self.resource_metrics.memory_usage_bytes = memory_bytes;
|
|
self.resource_metrics.peak_memory_bytes =
|
|
self.resource_metrics.peak_memory_bytes.max(memory_bytes);
|
|
self.resource_metrics.io_operations += io_ops;
|
|
|
|
// Add to historical data
|
|
let point = ResourceUsagePoint {
|
|
timestamp: Utc::now(),
|
|
cpu_percent,
|
|
memory_bytes,
|
|
io_ops,
|
|
};
|
|
|
|
if self.historical.resource_history.len() >= self.historical.max_history_points {
|
|
self.historical.resource_history.pop_front();
|
|
}
|
|
self.historical.resource_history.push_back(point);
|
|
}
|
|
|
|
/// Calculate throughput metrics
|
|
pub fn calculate_throughput(&mut self, time_window_seconds: f64) {
|
|
if time_window_seconds <= 0.0 || self.performance.records_processed == 0 {
|
|
return;
|
|
}
|
|
|
|
let records_per_second = self.performance.records_processed as f64 / time_window_seconds;
|
|
self.throughput_metrics.records_per_second = records_per_second;
|
|
self.throughput_metrics.peak_records_per_second = self
|
|
.throughput_metrics
|
|
.peak_records_per_second
|
|
.max(records_per_second);
|
|
|
|
// Add throughput point to history
|
|
let point = ThroughputPoint {
|
|
timestamp: Utc::now(),
|
|
throughput: records_per_second,
|
|
duration_seconds: time_window_seconds,
|
|
};
|
|
|
|
if self.historical.throughput_history.len() >= self.historical.max_history_points {
|
|
self.historical.throughput_history.pop_front();
|
|
}
|
|
self.historical.throughput_history.push_back(point);
|
|
|
|
// Calculate trend (simplified)
|
|
if self.historical.throughput_history.len() >= 5 {
|
|
let recent: Vec<f64> = self
|
|
.historical
|
|
.throughput_history
|
|
.iter()
|
|
.rev()
|
|
.take(5)
|
|
.map(|p| p.throughput)
|
|
.collect();
|
|
|
|
self.throughput_metrics.throughput_trend = self.calculate_trend(&recent);
|
|
}
|
|
}
|
|
|
|
/// Update quality metrics
|
|
pub fn update_quality_metrics(&mut self, quality_score: f64, sample_size: usize) {
|
|
// Update running average
|
|
let prev_total = self
|
|
.quality_metrics
|
|
.quality_by_dimension
|
|
.values()
|
|
.sum::<f64>();
|
|
let count = self.quality_metrics.quality_by_dimension.len() as f64;
|
|
|
|
if count == 0.0 {
|
|
self.quality_metrics.avg_quality_score = quality_score;
|
|
} else {
|
|
self.quality_metrics.avg_quality_score = (prev_total + quality_score) / (count + 1.0);
|
|
}
|
|
|
|
// Update quality distribution
|
|
match quality_score {
|
|
s if s >= 0.9 => self.quality_metrics.quality_distribution.excellent_count += 1,
|
|
s if s >= 0.7 => self.quality_metrics.quality_distribution.good_count += 1,
|
|
s if s >= 0.5 => self.quality_metrics.quality_distribution.fair_count += 1,
|
|
_ => self.quality_metrics.quality_distribution.poor_count += 1,
|
|
}
|
|
|
|
// Add to historical data
|
|
let point = QualityPoint {
|
|
timestamp: Utc::now(),
|
|
score: quality_score,
|
|
sample_size,
|
|
};
|
|
|
|
if self.historical.quality_history.len() >= self.historical.max_history_points {
|
|
self.historical.quality_history.pop_front();
|
|
}
|
|
self.historical.quality_history.push_back(point);
|
|
}
|
|
|
|
/// Get current metrics summary
|
|
pub fn summary(&self) -> MetricsSummary {
|
|
MetricsSummary {
|
|
total_validations: self.success_rates.total_validations,
|
|
success_rate: self.success_rates.success_rate,
|
|
avg_validation_time_ms: self.performance.avg_validation_time_ms,
|
|
current_throughput: self.throughput_metrics.records_per_second,
|
|
error_rate: self.error_metrics.error_rate_per_minute,
|
|
avg_quality_score: self.quality_metrics.avg_quality_score,
|
|
cpu_utilization: self.resource_metrics.cpu_utilization,
|
|
memory_usage_mb: self.resource_metrics.memory_usage_bytes as f64 / 1_000_000.0,
|
|
last_updated: self.metadata.last_updated,
|
|
}
|
|
}
|
|
|
|
/// Reset all metrics
|
|
pub fn reset(&mut self) {
|
|
*self = Self::new();
|
|
}
|
|
|
|
/// Export metrics to JSON
|
|
pub fn to_json(&self) -> Result<String, serde_json::Error> {
|
|
serde_json::to_string_pretty(self)
|
|
}
|
|
|
|
fn calculate_percentiles(&mut self) {
|
|
let mut times: Vec<f64> = self.historical.validation_times.iter().copied().collect();
|
|
times.sort_by(|a, b| a.total_cmp(b));
|
|
|
|
if !times.is_empty() {
|
|
let len = times.len();
|
|
self.performance.p50_validation_time_ms = times[len * 50 / 100];
|
|
self.performance.p95_validation_time_ms = times[len * 95 / 100];
|
|
self.performance.p99_validation_time_ms = times[len * 99 / 100];
|
|
}
|
|
}
|
|
|
|
fn calculate_trend(&self, values: &[f64]) -> TrendDirection {
|
|
if values.len() < 3 {
|
|
return TrendDirection::Unknown;
|
|
}
|
|
|
|
// Simple trend calculation based on linear regression slope
|
|
let n = values.len() as f64;
|
|
let sum_x = (0..values.len()).sum::<usize>() as f64;
|
|
let sum_y = values.iter().sum::<f64>();
|
|
let sum_xy = values
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, &y)| i as f64 * y)
|
|
.sum::<f64>();
|
|
let sum_x2 = (0..values.len()).map(|i| (i * i) as f64).sum::<f64>();
|
|
|
|
let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
|
|
|
|
match slope {
|
|
s if s > 0.1 => TrendDirection::Increasing,
|
|
s if s < -0.1 => TrendDirection::Decreasing,
|
|
s if s.abs() <= 0.1 => TrendDirection::Stable,
|
|
_ => TrendDirection::Volatile,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Condensed metrics summary
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MetricsSummary {
|
|
/// Total validations performed
|
|
pub total_validations: usize,
|
|
/// Overall success rate
|
|
pub success_rate: f64,
|
|
/// Average validation time
|
|
pub avg_validation_time_ms: f64,
|
|
/// Current throughput
|
|
pub current_throughput: f64,
|
|
/// Current error rate
|
|
pub error_rate: f64,
|
|
/// Average quality score
|
|
pub avg_quality_score: f64,
|
|
/// CPU utilization
|
|
pub cpu_utilization: f64,
|
|
/// Memory usage in MB
|
|
pub memory_usage_mb: f64,
|
|
/// Last update timestamp
|
|
pub last_updated: DateTime<Utc>,
|
|
}
|
|
|
|
impl Default for PerformanceMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
validation_duration_ms: 0,
|
|
avg_validation_time_ms: 0.0,
|
|
p50_validation_time_ms: 0.0,
|
|
p95_validation_time_ms: 0.0,
|
|
p99_validation_time_ms: 0.0,
|
|
max_validation_time_ms: 0,
|
|
min_validation_time_ms: 0,
|
|
rule_count: 0,
|
|
records_processed: 0,
|
|
queue_depth: 0,
|
|
active_validations: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for SuccessRateMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
total_validations: 0,
|
|
successful_validations: 0,
|
|
failed_validations: 0,
|
|
success_rate: 0.0,
|
|
hourly_success_rate: 0.0,
|
|
daily_success_rate: 0.0,
|
|
success_rate_trend: TrendDirection::Unknown,
|
|
first_validation: None,
|
|
last_validation: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for ErrorMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
total_errors: 0,
|
|
errors_by_type: HashMap::new(),
|
|
errors_by_severity: HashMap::new(),
|
|
error_rate_per_minute: 0.0,
|
|
error_frequency: Vec::new(),
|
|
top_error_types: Vec::new(),
|
|
error_resolution_stats: ErrorResolutionStats {
|
|
avg_resolution_time_minutes: 0.0,
|
|
unresolved_errors: 0,
|
|
escalation_rate: 0.0,
|
|
recovery_success_rate: 0.0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for ResourceMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
cpu_utilization: 0.0,
|
|
memory_usage_bytes: 0,
|
|
peak_memory_bytes: 0,
|
|
memory_utilization: 0.0,
|
|
io_operations: 0,
|
|
network_io: NetworkIoStats {
|
|
bytes_sent: 0,
|
|
bytes_received: 0,
|
|
packets_sent: 0,
|
|
packets_received: 0,
|
|
latency_ms: 0.0,
|
|
},
|
|
disk_io: DiskIoStats {
|
|
bytes_read: 0,
|
|
bytes_written: 0,
|
|
read_operations: 0,
|
|
write_operations: 0,
|
|
avg_io_wait_ms: 0.0,
|
|
},
|
|
thread_utilization: ThreadUtilization {
|
|
active_threads: 0,
|
|
total_threads: 0,
|
|
utilization_percentage: 0.0,
|
|
avg_queue_length: 0.0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for ThroughputMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
records_per_second: 0.0,
|
|
peak_records_per_second: 0.0,
|
|
hourly_avg_throughput: 0.0,
|
|
throughput_trend: TrendDirection::Unknown,
|
|
throughput_percentiles: ThroughputPercentiles {
|
|
p50: 0.0,
|
|
p75: 0.0,
|
|
p90: 0.0,
|
|
p95: 0.0,
|
|
p99: 0.0,
|
|
},
|
|
batch_stats: BatchStats {
|
|
total_batches: 0,
|
|
avg_batch_size: 0.0,
|
|
avg_batch_time_ms: 0.0,
|
|
batch_success_rate: 0.0,
|
|
},
|
|
streaming_stats: StreamingStats {
|
|
current_throughput: 0.0,
|
|
backlog_size: 0,
|
|
stream_latency_ms: 0.0,
|
|
buffer_utilization: 0.0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for QualityMetrics {
|
|
fn default() -> Self {
|
|
Self {
|
|
avg_quality_score: 0.0,
|
|
quality_distribution: QualityDistribution {
|
|
excellent_count: 0,
|
|
good_count: 0,
|
|
fair_count: 0,
|
|
poor_count: 0,
|
|
},
|
|
quality_trend: TrendDirection::Unknown,
|
|
quality_by_dimension: HashMap::new(),
|
|
anomaly_stats: AnomalyStats {
|
|
total_anomalies: 0,
|
|
anomalies_by_type: HashMap::new(),
|
|
detection_rate: 0.0,
|
|
false_positive_rate: 0.0,
|
|
},
|
|
schema_compliance: SchemaComplianceStats {
|
|
compliance_rate: 0.0,
|
|
violations_count: 0,
|
|
field_compliance: HashMap::new(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for MetricsConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
detailed_collection: true,
|
|
max_history_days: 30,
|
|
monitor_resources: true,
|
|
track_quality: true,
|
|
aggregation_interval_seconds: 60,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HistoricalMetrics {
|
|
fn new(max_points: usize) -> Self {
|
|
Self {
|
|
max_history_points: max_points,
|
|
validation_times: VecDeque::new(),
|
|
success_rates: VecDeque::new(),
|
|
throughput_history: VecDeque::new(),
|
|
resource_history: VecDeque::new(),
|
|
quality_history: VecDeque::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::time::Duration;
|
|
|
|
#[test]
|
|
fn test_metrics_creation() {
|
|
let metrics = ValidationMetrics::new();
|
|
assert_eq!(metrics.success_rates.total_validations, 0);
|
|
assert_eq!(metrics.performance.records_processed, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_record_validation() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
// Record successful validation
|
|
metrics.record_validation(Duration::from_millis(100), true);
|
|
|
|
assert_eq!(metrics.success_rates.total_validations, 1);
|
|
assert_eq!(metrics.success_rates.successful_validations, 1);
|
|
assert_eq!(metrics.success_rates.success_rate, 1.0);
|
|
assert_eq!(metrics.performance.avg_validation_time_ms, 100.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_record_error() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
metrics.record_error("validation_error", "critical");
|
|
|
|
assert_eq!(metrics.error_metrics.total_errors, 1);
|
|
assert_eq!(
|
|
metrics.error_metrics.errors_by_type.get("validation_error"),
|
|
Some(&1)
|
|
);
|
|
assert_eq!(
|
|
metrics.error_metrics.errors_by_severity.get("critical"),
|
|
Some(&1)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_success_rate_calculation() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
// Record mixed results
|
|
metrics.record_validation(Duration::from_millis(50), true);
|
|
metrics.record_validation(Duration::from_millis(75), false);
|
|
metrics.record_validation(Duration::from_millis(25), true);
|
|
|
|
assert_eq!(metrics.success_rates.total_validations, 3);
|
|
assert_eq!(metrics.success_rates.successful_validations, 2);
|
|
assert_eq!(metrics.success_rates.failed_validations, 1);
|
|
assert_eq!(metrics.success_rates.success_rate, 2.0 / 3.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_throughput_calculation() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
// Record some validations
|
|
metrics.record_validation(Duration::from_millis(100), true);
|
|
metrics.record_validation(Duration::from_millis(150), true);
|
|
|
|
// Calculate throughput over 2 seconds
|
|
metrics.calculate_throughput(2.0);
|
|
|
|
assert_eq!(metrics.throughput_metrics.records_per_second, 1.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_quality_metrics_update() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
metrics.update_quality_metrics(0.85, 100);
|
|
|
|
assert_eq!(metrics.quality_metrics.avg_quality_score, 0.85);
|
|
assert_eq!(metrics.quality_metrics.quality_distribution.good_count, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_trend_direction() {
|
|
let metrics = ValidationMetrics::new();
|
|
|
|
// Test increasing trend
|
|
let increasing = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
assert_eq!(
|
|
metrics.calculate_trend(&increasing),
|
|
TrendDirection::Increasing
|
|
);
|
|
|
|
// Test decreasing trend
|
|
let decreasing = vec![5.0, 4.0, 3.0, 2.0, 1.0];
|
|
assert_eq!(
|
|
metrics.calculate_trend(&decreasing),
|
|
TrendDirection::Decreasing
|
|
);
|
|
|
|
// Test stable trend
|
|
let stable = vec![3.0, 3.0, 3.0, 3.0, 3.0];
|
|
assert_eq!(metrics.calculate_trend(&stable), TrendDirection::Stable);
|
|
}
|
|
|
|
#[test]
|
|
fn test_metrics_summary() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
metrics.record_validation(Duration::from_millis(100), true);
|
|
metrics.update_quality_metrics(0.9, 50);
|
|
|
|
let summary = metrics.summary();
|
|
assert_eq!(summary.total_validations, 1);
|
|
assert_eq!(summary.success_rate, 1.0);
|
|
assert_eq!(summary.avg_quality_score, 0.9);
|
|
}
|
|
|
|
#[test]
|
|
fn test_metrics_reset() {
|
|
let mut metrics = ValidationMetrics::new();
|
|
|
|
metrics.record_validation(Duration::from_millis(100), true);
|
|
metrics.record_error("test_error", "warning");
|
|
|
|
assert_eq!(metrics.success_rates.total_validations, 1);
|
|
assert_eq!(metrics.error_metrics.total_errors, 1);
|
|
|
|
metrics.reset();
|
|
|
|
assert_eq!(metrics.success_rates.total_validations, 0);
|
|
assert_eq!(metrics.error_metrics.total_errors, 0);
|
|
}
|
|
}
|