Files
telecom-platform/apps/charging-engine/src/monitoring/monitoring.rs
T
nutcas3 386707d02f feat: Implement actual uptime calculation and memory usage tracking in monitoring
- Replace placeholder uptime with actual elapsed time since startup using startup_time
- Add fallback to UNIX epoch time if system time goes backwards
- Implement get_process_memory to read actual memory from /proc/self/status on Linux
- Add estimate_memory_usage fallback for non-Linux systems (35MB base estimate)
- Add get_redis_memory_usage to query Redis memory info as secondary fallback
- Update get_memory_usage to use
2026-04-14 20:08:37 +03:00

289 lines
10 KiB
Rust

use tracing::{info, debug};
use std::time::SystemTime;
use crate::errors::{ChargingError, ChargingResult, ErrorContext};
use super::types::{SystemStats, HealthStatus};
impl super::ChargingEngine {
pub async fn get_system_statistics(&self) -> ChargingResult<SystemStats> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
let active_sessions: u64 = conn.get("stats:active_sessions").await.unwrap_or(0);
let total_accounts: u64 = conn.keys("account:*").await.unwrap_or_default().len() as u64;
let blocked_users: u64 = conn.keys("block:*").await.unwrap_or_default().len() as u64;
let low_balance_alerts: u64 = conn.keys("alert:low_balance:*").await.unwrap_or_default().len() as u64;
let stats = SystemStats {
active_sessions,
total_accounts,
blocked_users,
low_balance_alerts,
uptime: self.get_uptime().await?,
};
Ok(stats)
}
async fn get_uptime(&self) -> ChargingResult<u64> {
// Calculate actual uptime since startup
match self.startup_time.elapsed() {
Ok(duration) => Ok(duration.as_secs()),
Err(_) => {
// If system time went backwards, fallback to current time
Ok(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs())
}
}
}
pub async fn health_check(&self) -> ChargingResult<HealthStatus> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
// Test Redis connection
let _: String = conn.get("health_check").await.unwrap_or_else(|_| "ok".to_string());
let status = HealthStatus {
redis_connected: true,
active_sync: true,
last_sync: chrono::Utc::now(),
memory_usage: self.get_memory_usage().await?,
};
Ok(status)
}
async fn get_memory_usage(&self) -> ChargingResult<u64> {
// Get actual memory usage from system
match self.get_process_memory() {
Ok(memory_bytes) => Ok(memory_bytes),
Err(_) => {
// Fallback to Redis memory usage if system memory fails
self.get_redis_memory_usage().await
}
}
}
fn get_process_memory(&self) -> ChargingResult<u64> {
use std::fs;
// Try to read from /proc/self/status for Linux systems
if let Ok(status) = fs::read_to_string("/proc/self/status") {
for line in status.lines() {
if line.starts_with("VmRSS:") {
if let Some(memory_str) = line.split_whitespace().nth(1) {
if let Ok(memory_kb) = memory_str.parse::<u64>() {
return Ok(memory_kb * 1024); // Convert KB to bytes
}
}
}
}
}
// Fallback for non-Linux systems or if reading fails
self.estimate_memory_usage()
}
fn estimate_memory_usage(&self) -> ChargingResult<u64> {
// Estimate memory usage based on known structures
// This is a rough estimate for non-Linux systems
let base_memory = 20_000_000; // 20MB base
let redis_connections = 5_000_000; // 5MB per connection estimate
let session_data = 10_000_000; // 10MB for session data
Ok(base_memory + redis_connections + session_data)
}
async fn get_redis_memory_usage(&self) -> ChargingResult<u64> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
// Get Redis memory info
let info: String = redis::cmd("INFO").query_async(&mut conn).await.unwrap_or_default();
// Parse memory usage from Redis info
for line in info.lines() {
if line.starts_with("used_memory:") {
if let Some(memory_str) = line.split(':').nth(1) {
if let Ok(memory_bytes) = memory_str.parse::<u64>() {
return Ok(memory_bytes);
}
}
}
}
// Fallback to estimate
self.estimate_memory_usage()
}
pub async fn get_performance_metrics(&self) -> ChargingResult<PerformanceMetrics> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
// Get Redis info
let info: String = redis::cmd("INFO").query_async(&mut conn).await.unwrap_or_default();
// Parse Redis info for metrics (simplified)
let connected_clients = self.extract_metric(&info, "connected_clients");
let used_memory = self.extract_metric(&info, "used_memory");
let total_commands_processed = self.extract_metric(&info, "total_commands_processed");
let metrics = PerformanceMetrics {
connected_clients,
used_memory,
total_commands_processed,
requests_per_second: self.calculate_rps().await?,
average_response_time: self.calculate_avg_response_time().await?,
};
Ok(metrics)
}
fn extract_metric(&self, info: &str, metric: &str) -> u64 {
info.lines()
.find(|line| line.starts_with(metric))
.and_then(|line| line.split(':').nth(1))
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(0)
}
async fn calculate_rps(&self) -> ChargingResult<f64> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
// Get Redis instantaneous ops per second
let info: String = redis::cmd("INFO").query_async(&mut conn).await.unwrap_or_default();
for line in info.lines() {
if line.starts_with("instantaneous_ops_per_sec:") {
if let Some(rps_str) = line.split(':').nth(1) {
if let Ok(rps) = rps_str.parse::<f64>() {
return Ok(rps);
}
}
}
}
// Fallback: calculate from total commands processed over time
let total_commands = self.extract_metric(&info, "total_commands_processed");
let uptime_seconds = self.get_uptime().await?;
if uptime_seconds > 0 {
Ok(total_commands as f64 / uptime_seconds as f64)
} else {
Ok(0.0)
}
}
async fn calculate_avg_response_time(&self) -> ChargingResult<f64> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
// Track response times in Redis for calculation
let response_times_key = "metrics:response_times";
// Get recent response times (last 100 operations)
let response_times: Vec<f64> = conn
.lrange(response_times_key, 0, 99)
.await
.unwrap_or_default()
.into_iter()
.filter_map(|s| s.parse::<f64>().ok())
.collect();
if response_times.is_empty() {
// If no response times tracked, estimate based on Redis latency
let latency_info = self.get_redis_latency(&mut conn).await?;
Ok(latency_info)
} else {
// Calculate average of recent response times
let sum: f64 = response_times.iter().sum();
Ok(sum / response_times.len() as f64)
}
}
async fn get_redis_latency(&self, conn: &mut redis::aio::Connection) -> ChargingResult<f64> {
use std::time::Instant;
// Measure Redis ping latency
let start = Instant::now();
let _: String = redis::cmd("PING").query_async(conn).await.unwrap_or_else(|_| "PONG".to_string());
let latency = start.elapsed();
Ok(latency.as_millis() as f64)
}
pub async fn get_error_statistics(&self) -> ChargingResult<ErrorStats> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
let pattern = "error:*".to_string();
let keys: Vec<String> = conn.keys(&pattern).await
.context("Failed to get error keys")?;
let mut total_errors = 0u64;
let mut error_types = std::collections::HashMap::new();
for key in keys {
if let Ok(count) = conn.get::<_, u64>(&key).await {
total_errors += count;
let error_type = key.split(':').nth(1).unwrap_or("unknown");
*error_types.entry(error_type.to_string()).or_insert(0) += count;
}
}
let stats = ErrorStats {
total_errors,
error_types,
last_error: self.get_last_error().await?,
};
Ok(stats)
}
async fn get_last_error(&self) -> ChargingResult<Option<String>> {
let mut conn = self.redis_client.get_async_connection().await
.context("Failed to get Redis connection")?;
let last_error: Option<String> = conn.get("last_error").await.unwrap_or(None);
Ok(last_error)
}
}
#[derive(Debug, Clone)]
pub struct SystemStats {
pub active_sessions: u64,
pub total_accounts: u64,
pub blocked_users: u64,
pub low_balance_alerts: u64,
pub uptime: u64,
}
#[derive(Debug, Clone)]
pub struct HealthStatus {
pub redis_connected: bool,
pub active_sync: bool,
pub last_sync: chrono::DateTime<chrono::Utc>,
pub memory_usage: u64,
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub connected_clients: u64,
pub used_memory: u64,
pub total_commands_processed: u64,
pub requests_per_second: f64,
pub average_response_time: f64,
}
#[derive(Debug, Clone)]
pub struct ErrorStats {
pub total_errors: u64,
pub error_types: std::collections::HashMap<String, u64>,
pub last_error: Option<String>,
}