use crate::error::{ModbusError, ModbusResult};
use std::time::{Duration, Instant};
use tracing::{debug, info, warn};
#[derive(Debug, Clone, Default)]
pub struct PerformanceMetrics {
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub total_duration: Duration,
pub min_duration: Option<Duration>,
pub max_duration: Option<Duration>,
pub avg_duration: Duration,
}
impl PerformanceMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_success(&mut self, duration: Duration) {
self.total_requests += 1;
self.successful_requests += 1;
self.total_duration += duration;
self.min_duration = Some(self.min_duration.map_or(duration, |min| min.min(duration)));
self.max_duration = Some(self.max_duration.map_or(duration, |max| max.max(duration)));
if self.total_requests > 0 {
self.avg_duration = self.total_duration / self.total_requests as u32;
}
}
pub fn record_failure(&mut self, duration: Duration) {
self.total_requests += 1;
self.failed_requests += 1;
self.total_duration += duration;
if self.total_requests > 0 {
self.avg_duration = self.total_duration / self.total_requests as u32;
}
}
pub fn success_rate(&self) -> f64 {
if self.total_requests == 0 {
return 0.0;
}
(self.successful_requests as f64 / self.total_requests as f64) * 100.0
}
pub fn requests_per_second(&self) -> f64 {
if self.total_duration.is_zero() {
return 0.0;
}
self.total_requests as f64 / self.total_duration.as_secs_f64()
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
pub struct OperationTimer {
start: Instant,
operation_name: String,
}
impl OperationTimer {
pub fn start(operation_name: &str) -> Self {
debug!("Starting operation: {}", operation_name);
Self {
start: Instant::now(),
operation_name: operation_name.to_string(),
}
}
pub fn stop(self) -> Duration {
let duration = self.start.elapsed();
debug!(
"Operation '{}' completed in {:?}",
self.operation_name, duration
);
duration
}
pub fn stop_and_log(self, success: bool) -> Duration {
let duration = self.start.elapsed();
if success {
info!(
"✅ Operation '{}' succeeded in {:?}",
self.operation_name, duration
);
} else {
warn!(
"❌ Operation '{}' failed after {:?}",
self.operation_name, duration
);
}
duration
}
}
pub mod validation {
use super::*;
pub fn validate_slave_id(slave_id: u8) -> ModbusResult<()> {
if slave_id == 0 || slave_id > 247 {
return Err(ModbusError::invalid_data(format!(
"Invalid slave ID: {} (must be 1-247)",
slave_id
)));
}
Ok(())
}
pub fn validate_address_range(start: u16, count: u16) -> ModbusResult<()> {
if count == 0 {
return Err(ModbusError::invalid_address(start, count));
}
if (start as u32 + count as u32) > 65536 {
return Err(ModbusError::invalid_address(start, count));
}
Ok(())
}
pub fn validate_register_count(count: u16) -> ModbusResult<()> {
if count == 0 || count > crate::MAX_READ_REGISTERS as u16 {
return Err(ModbusError::invalid_data(format!(
"Invalid register count: {} (must be 1-{})",
count,
crate::MAX_READ_REGISTERS
)));
}
Ok(())
}
pub fn validate_coil_count(count: u16) -> ModbusResult<()> {
if count == 0 || count > crate::MAX_READ_COILS as u16 {
return Err(ModbusError::invalid_data(format!(
"Invalid coil count: {} (must be 1-{})",
count,
crate::MAX_READ_COILS
)));
}
Ok(())
}
}
pub mod format {
use super::*;
use std::fmt::Write;
pub fn bytes_to_hex(bytes: &[u8]) -> String {
if bytes.is_empty() {
return String::new();
}
let mut result = String::with_capacity(bytes.len() * 3 - 1);
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
result.push(' ');
}
let _ = write!(result, "{:02X}", b);
}
result
}
pub fn registers_to_hex(registers: &[u16]) -> String {
if registers.is_empty() {
return String::new();
}
let mut result = String::with_capacity(registers.len() * 5 - 1);
for (i, r) in registers.iter().enumerate() {
if i > 0 {
result.push(' ');
}
let _ = write!(result, "{:04X}", r);
}
result
}
pub fn format_duration(duration: Duration) -> String {
let millis = duration.as_millis();
if millis < 1000 {
format!("{}ms", millis)
} else if millis < 60_000 {
format!("{:.2}s", duration.as_secs_f64())
} else {
let mins = millis / 60_000;
let secs = (millis % 60_000) as f64 / 1000.0;
format!("{}m {:.1}s", mins, secs)
}
}
pub fn format_transfer_rate(bytes: u64, duration: Duration) -> String {
if duration.is_zero() {
return "0 B/s".to_string();
}
let rate = bytes as f64 / duration.as_secs_f64();
if rate < 1024.0 {
format!("{:.1} B/s", rate)
} else if rate < 1024.0 * 1024.0 {
format!("{:.1} KB/s", rate / 1024.0)
} else {
format!("{:.1} MB/s", rate / (1024.0 * 1024.0))
}
}
pub fn format_metrics(metrics: &PerformanceMetrics) -> String {
format!(
"Performance Metrics:\n\
├─ Total Requests: {}\n\
├─ Successful: {} ({:.1}%)\n\
├─ Failed: {}\n\
├─ Average Duration: {}\n\
├─ Min Duration: {}\n\
├─ Max Duration: {}\n\
└─ Requests/sec: {:.1}",
metrics.total_requests,
metrics.successful_requests,
metrics.success_rate(),
metrics.failed_requests,
format_duration(metrics.avg_duration),
metrics
.min_duration
.map_or("N/A".to_string(), format_duration),
metrics
.max_duration
.map_or("N/A".to_string(), format_duration),
metrics.requests_per_second()
)
}
}
pub mod logging {
use super::*;
pub fn init_test_logger() {
}
pub fn log_request_response(
slave_id: u8,
function: &str,
address: u16,
data: &[u8],
duration: Duration,
success: bool,
) {
let status = if success { "✅" } else { "❌" };
let data_str = format::bytes_to_hex(data);
debug!(
"{} Slave {} {} @ {} | Data: {} | Duration: {}",
status,
slave_id,
function,
address,
data_str,
format::format_duration(duration)
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_metrics() {
let mut metrics = PerformanceMetrics::new();
metrics.record_success(Duration::from_millis(100));
metrics.record_success(Duration::from_millis(200));
metrics.record_failure(Duration::from_millis(150));
assert_eq!(metrics.total_requests, 3);
assert_eq!(metrics.successful_requests, 2);
assert_eq!(metrics.failed_requests, 1);
assert!((metrics.success_rate() - 66.67).abs() < 0.1);
}
#[test]
fn test_validation() {
assert!(validation::validate_slave_id(1).is_ok());
assert!(validation::validate_slave_id(247).is_ok());
assert!(validation::validate_slave_id(0).is_err());
assert!(validation::validate_slave_id(248).is_err());
assert!(validation::validate_address_range(0, 10).is_ok());
assert!(validation::validate_address_range(65530, 5).is_ok());
assert!(validation::validate_address_range(65530, 10).is_err());
}
#[test]
fn test_formatting() {
let bytes = vec![0x01, 0x03, 0x10, 0xFF];
assert_eq!(format::bytes_to_hex(&bytes), "01 03 10 FF");
let registers = vec![0x1234, 0x5678];
assert_eq!(format::registers_to_hex(®isters), "1234 5678");
let duration = Duration::from_millis(1500);
assert_eq!(format::format_duration(duration), "1.50s");
}
}