use std::fmt;
fn u64_to_f64(value: u64) -> f64 {
#[allow(clippy::cast_precision_loss)]
{
value as f64
}
}
fn scale_u64_by_ratio(value: u64, ratio: f64) -> u64 {
let scaled = u64_to_f64(value) * ratio;
if !scaled.is_finite() || scaled <= 0.0 {
return 0;
}
let capped = scaled.min(u64_to_f64(u64::MAX));
#[allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_sign_loss
)]
{
capped.round() as u64
}
}
#[derive(Debug, Clone, Copy)]
pub struct BackPressureLimits {
pub max_operations: u64,
pub max_bytes: u64,
pub soft_limit_ratio: f64,
}
impl Default for BackPressureLimits {
fn default() -> Self {
Self {
max_operations: 100_000, max_bytes: 10 * 1024 * 1024, soft_limit_ratio: 0.80, }
}
}
impl BackPressureLimits {
#[must_use]
pub fn new(max_operations: u64, max_bytes: u64) -> Self {
Self {
max_operations,
max_bytes,
soft_limit_ratio: 0.80,
}
}
#[must_use]
pub fn with_soft_ratio(mut self, ratio: f64) -> Self {
self.soft_limit_ratio = ratio.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn soft_operations(&self) -> u64 {
scale_u64_by_ratio(self.max_operations, self.soft_limit_ratio)
}
#[must_use]
pub fn soft_bytes(&self) -> u64 {
scale_u64_by_ratio(self.max_bytes, self.soft_limit_ratio)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackPressureReason {
OperationCount {
current: u64,
limit: u64,
},
ByteSize {
current: u64,
limit: u64,
},
Both {
op_usage_percent: u8,
byte_usage_percent: u8,
},
}
impl fmt::Display for BackPressureReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OperationCount { current, limit } => {
write!(f, "operation count {current}/{limit}")
}
Self::ByteSize { current, limit } => {
let current_mb = u64_to_f64(*current) / (1024.0 * 1024.0);
let limit_mb = u64_to_f64(*limit) / (1024.0 * 1024.0);
write!(f, "byte size {current_mb:.2}MB/{limit_mb:.2}MB")
}
Self::Both {
op_usage_percent,
byte_usage_percent,
} => {
write!(
f,
"operations at {op_usage_percent}%, bytes at {byte_usage_percent}%"
)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackPressureStatus {
Normal,
SoftLimit {
reason: BackPressureReason,
},
HardLimit {
reason: BackPressureReason,
},
}
impl BackPressureStatus {
#[must_use]
pub fn should_reject(&self) -> bool {
matches!(self, Self::HardLimit { .. })
}
#[must_use]
pub fn should_compact(&self) -> bool {
matches!(self, Self::SoftLimit { .. } | Self::HardLimit { .. })
}
}
impl fmt::Display for BackPressureStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => write!(f, "normal (below soft limit)"),
Self::SoftLimit { reason } => write!(f, "soft limit: {reason}"),
Self::HardLimit { reason } => write!(f, "HARD LIMIT: {reason}"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BackPressureController {
limits: BackPressureLimits,
}
impl BackPressureController {
#[must_use]
pub fn new(limits: BackPressureLimits) -> Self {
Self { limits }
}
#[must_use]
pub fn limits(&self) -> &BackPressureLimits {
&self.limits
}
pub fn set_limits(&mut self, limits: BackPressureLimits) {
self.limits = limits;
}
#[must_use]
pub fn check_pressure(&self, operations: u64, bytes: u64) -> BackPressureStatus {
let op_hard = operations >= self.limits.max_operations;
let byte_hard = bytes >= self.limits.max_bytes;
if op_hard || byte_hard {
let reason = Self::make_reason(
operations,
bytes,
self.limits.max_operations,
self.limits.max_bytes,
op_hard,
byte_hard,
);
return BackPressureStatus::HardLimit { reason };
}
let op_soft = operations >= self.limits.soft_operations();
let byte_soft = bytes >= self.limits.soft_bytes();
if op_soft || byte_soft {
let reason = Self::make_reason(
operations,
bytes,
self.limits.soft_operations(),
self.limits.soft_bytes(),
op_soft,
byte_soft,
);
return BackPressureStatus::SoftLimit { reason };
}
BackPressureStatus::Normal
}
#[must_use]
pub fn check_addition(
&self,
current_ops: u64,
current_bytes: u64,
add_ops: u64,
add_bytes: u64,
) -> BackPressureStatus {
let new_ops = current_ops.saturating_add(add_ops);
let new_bytes = current_bytes.saturating_add(add_bytes);
self.check_pressure(new_ops, new_bytes)
}
#[must_use]
pub fn utilization(&self, operations: u64, bytes: u64) -> (u8, u8) {
let op_pct = Self::calculate_percent(operations, self.limits.max_operations);
let byte_pct = Self::calculate_percent(bytes, self.limits.max_bytes);
(op_pct, byte_pct)
}
fn make_reason(
operations: u64,
bytes: u64,
op_limit: u64,
byte_limit: u64,
op_exceeded: bool,
byte_exceeded: bool,
) -> BackPressureReason {
match (op_exceeded, byte_exceeded) {
(true, true) => BackPressureReason::Both {
op_usage_percent: Self::calculate_percent(operations, op_limit),
byte_usage_percent: Self::calculate_percent(bytes, byte_limit),
},
(true, false) => BackPressureReason::OperationCount {
current: operations,
limit: op_limit,
},
(false, true) => BackPressureReason::ByteSize {
current: bytes,
limit: byte_limit,
},
(false, false) => {
BackPressureReason::OperationCount {
current: operations,
limit: op_limit,
}
}
}
}
fn calculate_percent(current: u64, limit: u64) -> u8 {
if limit == 0 {
return 100;
}
let pct = (current.saturating_mul(100)) / limit;
pct.min(100) as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_limits() {
let limits = BackPressureLimits::default();
assert_eq!(limits.max_operations, 100_000);
assert_eq!(limits.max_bytes, 10 * 1024 * 1024);
assert!((limits.soft_limit_ratio - 0.80).abs() < f64::EPSILON);
}
#[test]
fn test_soft_limit_calculations() {
let limits = BackPressureLimits::default();
assert_eq!(limits.soft_operations(), 80_000);
assert_eq!(limits.soft_bytes(), 8 * 1024 * 1024);
}
#[test]
fn test_custom_limits() {
let limits = BackPressureLimits::new(50_000, 5 * 1024 * 1024);
assert_eq!(limits.max_operations, 50_000);
assert_eq!(limits.soft_operations(), 40_000);
}
#[test]
fn test_custom_soft_ratio() {
let limits = BackPressureLimits::new(100_000, 10 * 1024 * 1024).with_soft_ratio(0.5);
assert_eq!(limits.soft_operations(), 50_000);
assert_eq!(limits.soft_bytes(), 5 * 1024 * 1024);
}
#[test]
fn test_controller_default() {
let controller = BackPressureController::default();
assert_eq!(controller.limits().max_operations, 100_000);
}
#[test]
fn test_check_pressure_normal() {
let controller = BackPressureController::default();
let status = controller.check_pressure(50_000, 5 * 1024 * 1024);
assert!(matches!(status, BackPressureStatus::Normal));
assert!(!status.should_reject());
assert!(!status.should_compact());
}
#[test]
fn test_check_pressure_soft_ops() {
let controller = BackPressureController::default();
let status = controller.check_pressure(85_000, 5 * 1024 * 1024);
assert!(matches!(status, BackPressureStatus::SoftLimit { .. }));
assert!(!status.should_reject());
assert!(status.should_compact());
}
#[test]
fn test_check_pressure_soft_bytes() {
let controller = BackPressureController::default();
let status = controller.check_pressure(50_000, 9 * 1024 * 1024);
assert!(matches!(status, BackPressureStatus::SoftLimit { .. }));
assert!(status.should_compact());
}
#[test]
fn test_check_pressure_hard_ops() {
let controller = BackPressureController::default();
let status = controller.check_pressure(100_000, 5 * 1024 * 1024);
assert!(matches!(status, BackPressureStatus::HardLimit { .. }));
assert!(status.should_reject());
assert!(status.should_compact());
}
#[test]
fn test_check_pressure_hard_bytes() {
let controller = BackPressureController::default();
let status = controller.check_pressure(50_000, 10 * 1024 * 1024);
assert!(matches!(status, BackPressureStatus::HardLimit { .. }));
assert!(status.should_reject());
}
#[test]
fn test_check_pressure_both_exceeded() {
let controller = BackPressureController::default();
let status = controller.check_pressure(110_000, 12 * 1024 * 1024);
match status {
BackPressureStatus::HardLimit { reason } => {
assert!(matches!(reason, BackPressureReason::Both { .. }));
}
_ => panic!("expected HardLimit"),
}
}
#[test]
fn test_check_addition_normal() {
let controller = BackPressureController::default();
let status = controller.check_addition(50_000, 5 * 1024 * 1024, 10_000, 1024 * 1024);
assert!(matches!(status, BackPressureStatus::Normal));
}
#[test]
fn test_check_addition_would_exceed() {
let controller = BackPressureController::default();
let status = controller.check_addition(80_000, 5 * 1024 * 1024, 30_000, 1024 * 1024);
assert!(matches!(status, BackPressureStatus::HardLimit { .. }));
}
#[test]
fn test_utilization() {
let controller = BackPressureController::default();
let (op_pct, byte_pct) = controller.utilization(50_000, 5 * 1024 * 1024);
assert_eq!(op_pct, 50);
assert_eq!(byte_pct, 50);
let (op_pct, byte_pct) = controller.utilization(100_000, 10 * 1024 * 1024);
assert_eq!(op_pct, 100);
assert_eq!(byte_pct, 100);
}
#[test]
fn test_reason_display() {
let op_reason = BackPressureReason::OperationCount {
current: 100_000,
limit: 100_000,
};
assert!(format!("{op_reason}").contains("100000"));
let byte_reason = BackPressureReason::ByteSize {
current: 10 * 1024 * 1024,
limit: 10 * 1024 * 1024,
};
assert!(format!("{byte_reason}").contains("10.00MB"));
let both_reason = BackPressureReason::Both {
op_usage_percent: 100,
byte_usage_percent: 100,
};
assert!(format!("{both_reason}").contains("100%"));
}
#[test]
fn test_status_display() {
let normal = BackPressureStatus::Normal;
assert!(format!("{normal}").contains("normal"));
let soft = BackPressureStatus::SoftLimit {
reason: BackPressureReason::OperationCount {
current: 85_000,
limit: 80_000,
},
};
assert!(format!("{soft}").contains("soft limit"));
let hard = BackPressureStatus::HardLimit {
reason: BackPressureReason::OperationCount {
current: 100_000,
limit: 100_000,
},
};
assert!(format!("{hard}").contains("HARD LIMIT"));
}
#[test]
fn test_set_limits() {
let mut controller = BackPressureController::default();
assert_eq!(controller.limits().max_operations, 100_000);
controller.set_limits(BackPressureLimits::new(50_000, 5 * 1024 * 1024));
assert_eq!(controller.limits().max_operations, 50_000);
}
#[test]
fn test_soft_ratio_clamping() {
let limits = BackPressureLimits::new(100_000, 10 * 1024 * 1024).with_soft_ratio(1.5);
assert!((limits.soft_limit_ratio - 1.0).abs() < f64::EPSILON);
let limits = BackPressureLimits::new(100_000, 10 * 1024 * 1024).with_soft_ratio(-0.5);
assert!((limits.soft_limit_ratio - 0.0).abs() < f64::EPSILON);
}
}