use crate::log_application;
use crate::logging::{Component, LogLevel, Timer};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub current_usage: u64,
pub peak_usage: u64,
pub active_connections: usize,
pub cached_telegrams: usize,
pub registered_devices: usize,
pub component_usage: ComponentMemoryUsage,
}
#[derive(Debug, Clone, Default)]
pub struct ComponentMemoryUsage {
pub transport: u64,
pub protocol: u64,
pub device: u64,
pub application: u64,
pub security: u64,
}
pub struct MemoryMonitor {
current_usage: AtomicU64,
peak_usage: AtomicU64,
memory_limit: u64,
component_usage: Arc<RwLock<ComponentMemoryUsage>>,
last_cleanup: Arc<RwLock<Instant>>,
}
impl MemoryMonitor {
#[must_use]
pub fn new(memory_limit_mb: u64) -> Self {
let memory_limit = memory_limit_mb * 1024 * 1024;
log_application!(
LogLevel::Info,
"Initializing memory monitor with limit: {} MB",
memory_limit_mb
);
Self {
current_usage: AtomicU64::new(0),
peak_usage: AtomicU64::new(0),
memory_limit,
component_usage: Arc::new(RwLock::new(ComponentMemoryUsage::default())),
last_cleanup: Arc::new(RwLock::new(Instant::now())),
}
}
pub async fn allocate(&self, component: Component, size: u64) -> Result<(), MemoryError> {
let new_usage = self.current_usage.fetch_add(size, Ordering::SeqCst) + size;
let mut peak = self.peak_usage.load(Ordering::SeqCst);
while new_usage > peak {
match self.peak_usage.compare_exchange_weak(
peak,
new_usage,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => break,
Err(current) => peak = current,
}
}
if new_usage > self.memory_limit {
log_application!(
LogLevel::Warn,
"Memory limit exceeded: {} bytes (limit: {} bytes)",
new_usage,
self.memory_limit
);
self.cleanup().await;
let current = self.current_usage.load(Ordering::SeqCst);
if current > self.memory_limit {
return Err(MemoryError::LimitExceeded {
current,
limit: self.memory_limit,
});
}
}
{
let mut usage = self.component_usage.write().await;
match component {
Component::Transport => usage.transport += size,
Component::Protocol => usage.protocol += size,
Component::Device => usage.device += size,
Component::Application => usage.application += size,
Component::Security => usage.security += size,
_ => {} }
}
log_application!(
LogLevel::Trace,
"Allocated {} bytes for {:?} (total: {} bytes)",
size,
component,
new_usage
);
Ok(())
}
pub async fn deallocate(&self, component: Component, size: u64) {
let new_usage = self
.current_usage
.fetch_sub(size, Ordering::SeqCst)
.saturating_sub(size);
{
let mut usage = self.component_usage.write().await;
match component {
Component::Transport => usage.transport = usage.transport.saturating_sub(size),
Component::Protocol => usage.protocol = usage.protocol.saturating_sub(size),
Component::Device => usage.device = usage.device.saturating_sub(size),
Component::Application => {
usage.application = usage.application.saturating_sub(size);
}
Component::Security => usage.security = usage.security.saturating_sub(size),
_ => {} }
}
log_application!(
LogLevel::Trace,
"Deallocated {} bytes for {:?} (total: {} bytes)",
size,
component,
new_usage
);
}
pub async fn get_stats(&self) -> MemoryStats {
let current_usage = self.current_usage.load(Ordering::SeqCst);
let peak_usage = self.peak_usage.load(Ordering::SeqCst);
let component_usage = self.component_usage.read().await.clone();
MemoryStats {
current_usage,
peak_usage,
active_connections: 0, cached_telegrams: 0, registered_devices: 0, component_usage,
}
}
pub fn is_within_bounds(&self) -> bool {
self.current_usage.load(Ordering::SeqCst) <= self.memory_limit
}
pub fn usage_percentage(&self) -> f64 {
let current = self.current_usage.load(Ordering::SeqCst) as f64;
let limit = self.memory_limit as f64;
(current / limit) * 100.0
}
pub async fn cleanup(&self) -> u64 {
let timer = Timer::start(Component::Application, "memory_cleanup");
let start_usage = self.current_usage.load(Ordering::SeqCst);
log_application!(
LogLevel::Info,
"Starting memory cleanup (current usage: {} bytes)",
start_usage
);
{
let mut last_cleanup = self.last_cleanup.write().await;
*last_cleanup = Instant::now();
}
let end_usage = self.current_usage.load(Ordering::SeqCst);
let freed = start_usage.saturating_sub(end_usage);
log_application!(
LogLevel::Info,
"Memory cleanup completed: freed {} bytes",
freed
);
timer.finish_with_message(&format!("Memory cleanup freed {freed} bytes"));
freed
}
pub async fn should_cleanup(&self) -> bool {
let usage_percentage = self.usage_percentage();
let last_cleanup = *self.last_cleanup.read().await;
let time_since_cleanup = last_cleanup.elapsed();
usage_percentage > 80.0 || time_since_cleanup > Duration::from_secs(5 * 60)
}
}
pub struct ConnectionPool<T: ?Sized> {
available: Arc<RwLock<Vec<Arc<T>>>>,
active: Arc<RwLock<Vec<Arc<T>>>>,
max_size: usize,
create_fn: Arc<dyn Fn() -> crate::Result<Arc<T>> + Send + Sync>,
memory_monitor: Arc<MemoryMonitor>,
}
impl<T: ?Sized + Send + Sync + 'static> ConnectionPool<T> {
pub fn new<F>(max_size: usize, create_fn: F, memory_monitor: Arc<MemoryMonitor>) -> Self
where
F: Fn() -> crate::Result<Arc<T>> + Send + Sync + 'static,
{
log_application!(
LogLevel::Info,
"Creating connection pool with max size: {}",
max_size
);
Self {
available: Arc::new(RwLock::new(Vec::new())),
active: Arc::new(RwLock::new(Vec::new())),
max_size,
create_fn: Arc::new(create_fn),
memory_monitor,
}
}
pub async fn get_connection(&self) -> Result<PooledConnection<T>, MemoryError> {
{
let mut available = self.available.write().await;
if let Some(conn) = available.pop() {
let mut active = self.active.write().await;
active.push(conn.clone());
log_application!(
LogLevel::Trace,
"Reused connection from pool (active: {}, available: {})",
active.len(),
available.len()
);
return Ok(PooledConnection::new(
conn,
self.available.clone(),
self.active.clone(),
));
}
}
{
let active = self.active.read().await;
if active.len() >= self.max_size {
return Err(MemoryError::PoolExhausted {
max_size: self.max_size,
current_size: active.len(),
});
}
}
let conn = (self.create_fn)().map_err(|e| MemoryError::ConnectionCreationFailed {
reason: e.to_string(),
})?;
let conn_size = 1024u64; self.memory_monitor
.allocate(Component::Transport, conn_size)
.await?;
{
let mut active = self.active.write().await;
active.push(conn.clone());
log_application!(
LogLevel::Debug,
"Created new connection (active: {}, available: {})",
active.len(),
0
);
}
Ok(PooledConnection::new(
conn,
self.available.clone(),
self.active.clone(),
))
}
pub async fn get_stats(&self) -> PoolStats {
let available = self.available.read().await;
let active = self.active.read().await;
PoolStats {
max_size: self.max_size,
active_connections: active.len(),
available_connections: available.len(),
total_connections: active.len() + available.len(),
}
}
pub async fn cleanup(&self) -> usize {
let mut available = self.available.write().await;
let initial_count = available.len();
let keep_count = available.len().div_ceil(2);
available.truncate(keep_count);
let removed = initial_count - available.len();
if removed > 0 {
log_application!(
LogLevel::Debug,
"Cleaned up {} unused connections from pool",
removed
);
}
removed
}
}
pub struct PooledConnection<T: ?Sized + Send + Sync + 'static> {
connection: Option<Arc<T>>,
available: Arc<RwLock<Vec<Arc<T>>>>,
active: Arc<RwLock<Vec<Arc<T>>>>,
}
impl<T: ?Sized + Send + Sync + 'static> PooledConnection<T> {
fn new(
connection: Arc<T>,
available: Arc<RwLock<Vec<Arc<T>>>>,
active: Arc<RwLock<Vec<Arc<T>>>>,
) -> Self {
Self {
connection: Some(connection),
available,
active,
}
}
#[must_use]
pub fn get(&self) -> &T {
self.connection
.as_ref()
.expect("connection is only None after Drop, which consumes self")
}
}
impl<T: ?Sized + Send + Sync + 'static> Drop for PooledConnection<T> {
fn drop(&mut self) {
if let Some(conn) = self.connection.take() {
let available = self.available.clone();
let active = self.active.clone();
tokio::spawn(async move {
{
let mut active_guard = active.write().await;
active_guard.retain(|c| !Arc::ptr_eq(c, &conn));
}
{
let mut available_guard = available.write().await;
available_guard.push(conn);
}
});
}
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub max_size: usize,
pub active_connections: usize,
pub available_connections: usize,
pub total_connections: usize,
}
#[derive(Debug, thiserror::Error)]
pub enum MemoryError {
#[error("Memory limit exceeded: current {current} bytes, limit {limit} bytes")]
LimitExceeded { current: u64, limit: u64 },
#[error("Connection pool exhausted: {current_size}/{max_size} connections")]
PoolExhausted {
max_size: usize,
current_size: usize,
},
#[error("Failed to create connection: {reason}")]
ConnectionCreationFailed { reason: String },
}
pub struct PerformanceOptimizer {
memory_monitor: Arc<MemoryMonitor>,
hot_path_stats: Arc<RwLock<HotPathStats>>,
}
impl PerformanceOptimizer {
pub fn new(memory_monitor: Arc<MemoryMonitor>) -> Self {
Self {
memory_monitor,
hot_path_stats: Arc::new(RwLock::new(HotPathStats::default())),
}
}
pub async fn record_hot_path(&self, path: &str, duration: Duration) {
let mut stats = self.hot_path_stats.write().await;
let entry = stats
.paths
.entry(path.to_string())
.or_insert_with(HotPathEntry::default);
entry.call_count += 1;
entry.total_duration += duration;
entry.min_duration = entry.min_duration.min(duration);
entry.max_duration = entry.max_duration.max(duration);
entry.avg_duration = entry.total_duration / entry.call_count as u32;
}
pub async fn get_hot_path_stats(&self) -> HotPathStats {
self.hot_path_stats.read().await.clone()
}
pub async fn optimize(&self) {
let stats = self.hot_path_stats.read().await;
for (path, entry) in &stats.paths {
if entry.avg_duration > Duration::from_millis(10) {
log_application!(
LogLevel::Warn,
"Slow hot path detected: {} (avg: {:?})",
path,
entry.avg_duration
);
}
}
if self.memory_monitor.should_cleanup().await {
self.memory_monitor.cleanup().await;
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HotPathStats {
pub paths: std::collections::HashMap<String, HotPathEntry>,
}
#[derive(Debug, Clone)]
pub struct HotPathEntry {
pub call_count: u64,
pub total_duration: Duration,
pub avg_duration: Duration,
pub min_duration: Duration,
pub max_duration: Duration,
}
impl Default for HotPathEntry {
fn default() -> Self {
Self {
call_count: 0,
total_duration: Duration::ZERO,
avg_duration: Duration::ZERO,
min_duration: Duration::MAX,
max_duration: Duration::ZERO,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use std::sync::Arc;
use crate::logging::Component;
#[test]
fn property_memory_usage_stability() {
proptest!(|(
operations in prop::collection::vec(
(
prop::sample::select(vec![
Component::Transport,
Component::Protocol,
Component::Device,
Component::Application,
Component::Security,
]),
1u64..1024u64, any::<bool>(), ),
1..100
)
)| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let monitor = MemoryMonitor::new(10); let initial_usage = monitor.get_stats().await.current_usage;
let mut allocated_sizes: std::collections::HashMap<Component, Vec<u64>> =
std::collections::HashMap::new();
for (component, size, should_allocate) in operations {
if should_allocate {
if monitor.allocate(component, size).await.is_ok() {
allocated_sizes.entry(component)
.or_default()
.push(size);
}
} else {
if let Some(sizes) = allocated_sizes.get_mut(&component)
&& let Some(allocated_size) = sizes.pop() {
monitor.deallocate(component, allocated_size).await;
}
}
}
for (component, sizes) in allocated_sizes {
for size in sizes {
monitor.deallocate(component, size).await;
}
}
let final_usage = monitor.get_stats().await.current_usage;
let usage_diff = final_usage.abs_diff(initial_usage);
prop_assert!(usage_diff <= 1024,
"Memory usage not stable: initial={}, final={}, diff={}",
initial_usage, final_usage, usage_diff);
prop_assert!(monitor.is_within_bounds(),
"Memory usage exceeded bounds: {}%",
monitor.usage_percentage());
Ok(())
})?;
});
}
#[tokio::test]
async fn test_memory_monitor_basic_operations() {
let monitor = MemoryMonitor::new(1);
assert!(
monitor
.allocate(Component::Transport, 512 * 1024)
.await
.is_ok()
);
let stats = monitor.get_stats().await;
assert_eq!(stats.current_usage, 512 * 1024);
assert!(stats.peak_usage >= 512 * 1024);
monitor.deallocate(Component::Transport, 512 * 1024).await;
let stats = monitor.get_stats().await;
assert_eq!(stats.current_usage, 0);
}
#[tokio::test]
async fn test_memory_monitor_limit_exceeded() {
let monitor = MemoryMonitor::new(1);
let result = monitor
.allocate(Component::Transport, 2 * 1024 * 1024)
.await;
assert!(result.is_err());
if let Err(MemoryError::LimitExceeded { current, limit }) = result {
assert!(current > limit);
} else {
panic!("Expected LimitExceeded error");
}
}
#[tokio::test]
async fn test_connection_pool_basic_operations() {
#[derive(Debug)]
struct TestConnection;
let memory_monitor = Arc::new(MemoryMonitor::new(10));
let pool = ConnectionPool::new(
3, || Ok(Arc::new(TestConnection)),
memory_monitor,
);
let conn1 = pool.get_connection().await.unwrap();
let _conn2 = pool.get_connection().await.unwrap();
let _conn3 = pool.get_connection().await.unwrap();
let result = pool.get_connection().await;
assert!(matches!(result, Err(MemoryError::PoolExhausted { .. })));
drop(conn1);
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let stats = pool.get_stats().await;
assert_eq!(stats.max_size, 3);
}
#[tokio::test]
async fn test_performance_optimizer() {
let memory_monitor = Arc::new(MemoryMonitor::new(10));
let optimizer = PerformanceOptimizer::new(memory_monitor);
optimizer
.record_hot_path("test_path", std::time::Duration::from_millis(5))
.await;
optimizer
.record_hot_path("test_path", std::time::Duration::from_millis(10))
.await;
optimizer
.record_hot_path("test_path", std::time::Duration::from_millis(15))
.await;
let stats = optimizer.get_hot_path_stats().await;
assert!(stats.paths.contains_key("test_path"));
let entry = &stats.paths["test_path"];
assert_eq!(entry.call_count, 3);
assert_eq!(entry.avg_duration, std::time::Duration::from_millis(10));
assert_eq!(entry.min_duration, std::time::Duration::from_millis(5));
assert_eq!(entry.max_duration, std::time::Duration::from_millis(15));
}
#[tokio::test]
async fn test_memory_cleanup() {
let monitor = MemoryMonitor::new(10);
monitor.allocate(Component::Transport, 1024).await.unwrap();
monitor.allocate(Component::Protocol, 2048).await.unwrap();
let stats_before = monitor.get_stats().await;
assert_eq!(stats_before.current_usage, 3072);
let _freed = monitor.cleanup().await;
let stats_after = monitor.get_stats().await;
assert_eq!(stats_after.current_usage, stats_before.current_usage);
}
#[test]
fn test_memory_error_display() {
let error = MemoryError::LimitExceeded {
current: 1000,
limit: 500,
};
assert!(error.to_string().contains("Memory limit exceeded"));
let error = MemoryError::PoolExhausted {
max_size: 10,
current_size: 10,
};
assert!(error.to_string().contains("Connection pool exhausted"));
let error = MemoryError::ConnectionCreationFailed {
reason: "test".to_string(),
};
assert!(error.to_string().contains("Failed to create connection"));
}
}