use crate::error::Result;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::Level;
#[derive(Debug, Clone)]
pub struct ProductionConfig {
pub logging_enabled: bool,
pub log_level: LogLevel,
pub metrics_enabled: bool,
pub health_checks_enabled: bool,
pub profiling_enabled: bool,
pub benchmarking_enabled: bool,
pub histogram_enabled: bool,
pub app_name: String,
pub environment: String,
pub metrics_interval_secs: u64,
}
impl Default for ProductionConfig {
fn default() -> Self {
Self {
logging_enabled: true,
log_level: LogLevel::Info,
metrics_enabled: true,
health_checks_enabled: true,
profiling_enabled: true,
benchmarking_enabled: false, histogram_enabled: true,
app_name: "oxirs-samm".to_string(),
environment: "production".to_string(),
metrics_interval_secs: 60, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl From<LogLevel> for Level {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Trace => Level::TRACE,
LogLevel::Debug => Level::DEBUG,
LogLevel::Info => Level::INFO,
LogLevel::Warn => Level::WARN,
LogLevel::Error => Level::ERROR,
}
}
}
pub fn init_production(config: &ProductionConfig) -> Result<()> {
if config.logging_enabled {
init_logging(config);
}
if config.metrics_enabled {
MetricsCollector::global().reset();
}
tracing::info!(
app = %config.app_name,
env = %config.environment,
"Production initialization complete"
);
Ok(())
}
fn init_logging(config: &ProductionConfig) {
let level: Level = config.log_level.into();
tracing_subscriber::fmt()
.with_max_level(level)
.with_target(true)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true)
.json()
.init();
}
#[derive(Debug)]
pub struct MetricsCollector {
operations_total: AtomicU64,
errors_total: AtomicU64,
warnings_total: AtomicU64,
parse_operations: AtomicU64,
validation_operations: AtomicU64,
codegen_operations: AtomicU64,
package_operations: AtomicU64,
active_operations: AtomicU64,
start_time: Instant,
}
impl MetricsCollector {
pub fn new() -> Self {
Self {
operations_total: AtomicU64::new(0),
errors_total: AtomicU64::new(0),
warnings_total: AtomicU64::new(0),
parse_operations: AtomicU64::new(0),
validation_operations: AtomicU64::new(0),
codegen_operations: AtomicU64::new(0),
package_operations: AtomicU64::new(0),
active_operations: AtomicU64::new(0),
start_time: Instant::now(),
}
}
pub fn global() -> &'static MetricsCollector {
static METRICS: once_cell::sync::Lazy<MetricsCollector> =
once_cell::sync::Lazy::new(MetricsCollector::new);
&METRICS
}
pub fn record_operation(&self, operation_type: OperationType) {
self.operations_total.fetch_add(1, Ordering::Relaxed);
match operation_type {
OperationType::Parse => {
self.parse_operations.fetch_add(1, Ordering::Relaxed);
}
OperationType::Validation => {
self.validation_operations.fetch_add(1, Ordering::Relaxed);
}
OperationType::CodeGeneration => {
self.codegen_operations.fetch_add(1, Ordering::Relaxed);
}
OperationType::Package => {
self.package_operations.fetch_add(1, Ordering::Relaxed);
}
}
tracing::debug!(operation_type = ?operation_type, "Operation recorded");
}
pub fn record_operation_with_duration(&self, operation_type: OperationType, _duration_ms: f64) {
self.record_operation(operation_type);
}
pub fn record_error(&self) {
self.errors_total.fetch_add(1, Ordering::Relaxed);
tracing::warn!(
errors_total = self.errors_total.load(Ordering::Relaxed),
"Error recorded"
);
}
pub fn record_warning(&self) {
self.warnings_total.fetch_add(1, Ordering::Relaxed);
}
pub fn start_operation() -> OperationGuard {
let metrics = Self::global();
metrics.active_operations.fetch_add(1, Ordering::SeqCst);
OperationGuard { metrics }
}
fn end_operation(&self) {
self.active_operations.fetch_sub(1, Ordering::SeqCst);
}
pub fn active_operations(&self) -> u64 {
self.active_operations.load(Ordering::Relaxed)
}
pub fn snapshot(&self) -> MetricsSnapshot {
MetricsSnapshot {
operations_total: self.operations_total.load(Ordering::Relaxed),
errors_total: self.errors_total.load(Ordering::Relaxed),
warnings_total: self.warnings_total.load(Ordering::Relaxed),
parse_operations: self.parse_operations.load(Ordering::Relaxed),
validation_operations: self.validation_operations.load(Ordering::Relaxed),
codegen_operations: self.codegen_operations.load(Ordering::Relaxed),
package_operations: self.package_operations.load(Ordering::Relaxed),
operation_duration_p50: 0.0, operation_duration_p95: 0.0,
operation_duration_p99: 0.0,
active_operations: self.active_operations.load(Ordering::Relaxed),
uptime: self.start_time.elapsed(),
}
}
pub fn reset(&self) {
self.operations_total.store(0, Ordering::Relaxed);
self.errors_total.store(0, Ordering::Relaxed);
self.warnings_total.store(0, Ordering::Relaxed);
self.parse_operations.store(0, Ordering::Relaxed);
self.validation_operations.store(0, Ordering::Relaxed);
self.codegen_operations.store(0, Ordering::Relaxed);
self.package_operations.store(0, Ordering::Relaxed);
tracing::info!("Metrics reset complete");
}
}
impl Default for MetricsCollector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct OperationGuard {
metrics: &'static MetricsCollector,
}
impl Drop for OperationGuard {
fn drop(&mut self) {
self.metrics.end_operation();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationType {
Parse,
Validation,
CodeGeneration,
Package,
}
#[derive(Debug, Clone)]
pub struct MetricsSnapshot {
pub operations_total: u64,
pub errors_total: u64,
pub warnings_total: u64,
pub parse_operations: u64,
pub validation_operations: u64,
pub codegen_operations: u64,
pub package_operations: u64,
pub operation_duration_p50: f64,
pub operation_duration_p95: f64,
pub operation_duration_p99: f64,
pub active_operations: u64,
pub uptime: Duration,
}
impl MetricsSnapshot {
pub fn error_rate(&self) -> f64 {
if self.operations_total == 0 {
0.0
} else {
self.errors_total as f64 / self.operations_total as f64
}
}
pub fn ops_per_second(&self) -> f64 {
let secs = self.uptime.as_secs_f64();
if secs == 0.0 {
0.0
} else {
self.operations_total as f64 / secs
}
}
pub fn avg_duration_ms(&self) -> f64 {
self.operation_duration_p50
}
pub fn is_performance_degraded(&self, threshold_ms: f64) -> bool {
self.operation_duration_p95 > threshold_ms
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone)]
pub struct HealthCheck {
pub status: HealthStatus,
pub checks: Vec<(String, HealthStatus, String)>,
pub timestamp: Instant,
}
pub fn health_check() -> HealthCheck {
let mut checks = Vec::new();
let mut overall_status = HealthStatus::Healthy;
let metrics = MetricsCollector::global().snapshot();
let error_rate = metrics.error_rate();
if error_rate > 0.5 {
checks.push((
"error_rate".to_string(),
HealthStatus::Unhealthy,
format!("High error rate: {:.2}%", error_rate * 100.0),
));
overall_status = HealthStatus::Unhealthy;
} else if error_rate > 0.1 {
checks.push((
"error_rate".to_string(),
HealthStatus::Degraded,
format!("Elevated error rate: {:.2}%", error_rate * 100.0),
));
if overall_status == HealthStatus::Healthy {
overall_status = HealthStatus::Degraded;
}
} else {
checks.push((
"error_rate".to_string(),
HealthStatus::Healthy,
format!("Normal error rate: {:.2}%", error_rate * 100.0),
));
}
checks.push((
"latency_p95".to_string(),
HealthStatus::Healthy,
"Latency tracking pending histogram API".to_string(),
));
checks.push((
"active_operations".to_string(),
HealthStatus::Healthy,
"Active operation tracking pending".to_string(),
));
checks.push((
"uptime".to_string(),
HealthStatus::Healthy,
format!("{} seconds", metrics.uptime.as_secs()),
));
let ops_per_sec = metrics.ops_per_second();
checks.push((
"throughput".to_string(),
HealthStatus::Healthy,
format!("{:.2} ops/sec", ops_per_sec),
));
HealthCheck {
status: overall_status,
checks,
timestamp: Instant::now(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_production_config_default() {
let config = ProductionConfig::default();
assert!(config.logging_enabled);
assert!(config.metrics_enabled);
assert_eq!(config.app_name, "oxirs-samm");
}
#[test]
fn test_metrics_collector() {
let collector = MetricsCollector::new();
collector.record_operation(OperationType::Parse);
collector.record_operation(OperationType::Validation);
collector.record_error();
let snapshot = collector.snapshot();
assert_eq!(snapshot.operations_total, 2);
assert_eq!(snapshot.errors_total, 1);
assert_eq!(snapshot.parse_operations, 1);
assert_eq!(snapshot.validation_operations, 1);
}
#[test]
fn test_error_rate_calculation() {
let snapshot = MetricsSnapshot {
operations_total: 100,
errors_total: 10,
warnings_total: 5,
parse_operations: 50,
validation_operations: 30,
codegen_operations: 15,
package_operations: 5,
operation_duration_p50: 100.0,
operation_duration_p95: 500.0,
operation_duration_p99: 1000.0,
active_operations: 5,
uptime: Duration::from_secs(60),
};
assert!((snapshot.error_rate() - 0.1).abs() < 0.01);
assert!((snapshot.ops_per_second() - (100.0 / 60.0)).abs() < 0.01);
assert_eq!(snapshot.avg_duration_ms(), 100.0);
assert!(!snapshot.is_performance_degraded(600.0));
assert!(snapshot.is_performance_degraded(400.0));
}
#[test]
fn test_health_check() {
let health = health_check();
assert!(!health.checks.is_empty());
assert!(health.checks.len() >= 5);
assert!(
health.status == HealthStatus::Healthy
|| health.status == HealthStatus::Degraded
|| health.status == HealthStatus::Unhealthy
);
}
#[test]
fn test_metrics_collector_with_duration() {
let collector = MetricsCollector::new();
collector.record_operation(OperationType::Parse);
collector.record_operation_with_duration(OperationType::Validation, 150.5);
let snapshot = collector.snapshot();
assert_eq!(snapshot.operations_total, 2);
assert_eq!(snapshot.parse_operations, 1);
assert_eq!(snapshot.validation_operations, 1);
}
#[test]
fn test_production_config_with_profiling() {
let config = ProductionConfig::default();
assert!(config.profiling_enabled);
assert!(config.histogram_enabled);
assert!(!config.benchmarking_enabled); assert_eq!(config.metrics_interval_secs, 60);
}
}