use nutype::nutype;
use std::fmt;
#[allow(clippy::cast_precision_loss)] const fn pool_count_as_f64(value: usize) -> f64 {
value as f64
}
macro_rules! pool_count_type {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[nutype(derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, From
))]
pub struct $name(usize);
impl $name {
#[inline]
pub fn zero() -> Self {
Self::new(0)
}
#[inline]
#[must_use]
pub fn get(&self) -> usize {
self.into_inner()
}
}
};
}
pool_count_type!(
AvailableConnections
);
pool_count_type!(
MaxPoolSize
);
pool_count_type!(
CreatedConnections
);
pool_count_type!(
InUseConnections
);
impl InUseConnections {
#[inline]
#[must_use]
pub fn from_pool_stats(max: MaxPoolSize, available: AvailableConnections) -> Self {
Self::new(max.get().saturating_sub(available.get()))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PoolUtilization(f64);
impl PoolUtilization {
#[inline]
#[must_use]
pub fn new(percentage: f64) -> Self {
assert!(
(0.0..=100.0).contains(&percentage),
"Utilization must be 0-100%, got {percentage}"
);
Self(percentage)
}
#[inline]
#[must_use]
pub fn from_pool_stats(max: MaxPoolSize, available: AvailableConnections) -> Self {
let max_size = max.get();
if max_size == 0 {
return Self(0.0);
}
let in_use = max_size.saturating_sub(available.get());
let utilization = (pool_count_as_f64(in_use) / pool_count_as_f64(max_size)) * 100.0;
Self(utilization)
}
#[inline]
#[must_use]
pub const fn as_percentage(self) -> f64 {
self.0
}
#[inline]
#[must_use]
pub fn is_full(self) -> bool {
self.0 >= 100.0
}
#[inline]
#[must_use]
pub fn is_empty(self) -> bool {
self.0 == 0.0
}
#[inline]
#[must_use]
pub fn is_high_load(self) -> bool {
self.0 >= 80.0
}
}
impl fmt::Display for PoolUtilization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}%", self.0)
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
#[test]
fn test_available_connections() {
let available = AvailableConnections::new(5);
assert_eq!(available.get(), 5);
assert_eq!(format!("{available}"), "5");
let zero = AvailableConnections::zero();
assert_eq!(zero.get(), 0);
}
#[test]
fn test_max_pool_size() {
let max = MaxPoolSize::new(10);
assert_eq!(max.get(), 10);
assert_eq!(format!("{max}"), "10");
}
#[test]
fn test_created_connections() {
let created = CreatedConnections::new(25);
assert_eq!(created.get(), 25);
assert_eq!(format!("{created}"), "25");
let zero = CreatedConnections::zero();
assert_eq!(zero.get(), 0);
}
#[test]
fn test_in_use_connections() {
let max = MaxPoolSize::new(10);
let available = AvailableConnections::new(3);
let in_use = InUseConnections::from_pool_stats(max, available);
assert_eq!(in_use.get(), 7);
}
#[test]
fn test_in_use_saturating() {
let max = MaxPoolSize::new(5);
let available = AvailableConnections::new(10);
let in_use = InUseConnections::from_pool_stats(max, available);
assert_eq!(in_use.get(), 0);
}
#[test]
fn test_pool_utilization() {
let max = MaxPoolSize::new(10);
let available = AvailableConnections::new(3);
let utilization = PoolUtilization::from_pool_stats(max, available);
assert_eq!(utilization.as_percentage(), 70.0);
assert_eq!(format!("{utilization}"), "70.0%");
}
#[test]
fn test_pool_utilization_full() {
let max = MaxPoolSize::new(10);
let available = AvailableConnections::new(0);
let utilization = PoolUtilization::from_pool_stats(max, available);
assert!(utilization.is_full());
assert!(utilization.is_high_load());
assert!(!utilization.is_empty());
}
#[test]
fn test_pool_utilization_empty() {
let max = MaxPoolSize::new(10);
let available = AvailableConnections::new(10);
let utilization = PoolUtilization::from_pool_stats(max, available);
assert!(utilization.is_empty());
assert!(!utilization.is_full());
assert!(!utilization.is_high_load());
}
#[test]
fn test_pool_utilization_high_load() {
let max = MaxPoolSize::new(10);
let available = AvailableConnections::new(1);
let utilization = PoolUtilization::from_pool_stats(max, available);
assert!(utilization.is_high_load());
assert!(!utilization.is_full());
}
#[test]
fn test_pool_utilization_zero_max() {
let max = MaxPoolSize::new(0);
let available = AvailableConnections::new(0);
let utilization = PoolUtilization::from_pool_stats(max, available);
assert_eq!(utilization.as_percentage(), 0.0);
}
#[test]
#[should_panic(expected = "Utilization must be 0-100%")]
fn test_pool_utilization_invalid() {
let _ = PoolUtilization::new(150.0);
}
#[test]
fn test_ordering() {
let small = AvailableConnections::new(1);
let large = AvailableConnections::new(10);
assert!(small < large);
assert_eq!(small, small);
}
#[test]
fn test_from_conversions() {
let available: AvailableConnections = 5usize.into();
assert_eq!(available.get(), 5);
let max: MaxPoolSize = 10usize.into();
assert_eq!(max.get(), 10);
let created: CreatedConnections = 25usize.into();
assert_eq!(created.get(), 25);
}
}