use std::time::{Duration, Instant};
const BANDWIDTH_WINDOW: Duration = Duration::from_secs(1);
pub struct BandwidthThrottle {
bytes_sent_this_window: u32,
bytes_received_this_window: u32,
bandwidth_window_start: Instant,
outgoing_bandwidth_limit: u32,
incoming_bandwidth_limit: u32,
}
impl BandwidthThrottle {
pub fn new(outgoing_limit: u32, incoming_limit: u32, start_time: Instant) -> Self {
Self {
bytes_sent_this_window: 0,
bytes_received_this_window: 0,
bandwidth_window_start: start_time,
outgoing_bandwidth_limit: outgoing_limit,
incoming_bandwidth_limit: incoming_limit,
}
}
pub fn update_bandwidth_window(&mut self, time: Instant) -> bool {
if time.duration_since(self.bandwidth_window_start) >= BANDWIDTH_WINDOW {
self.bytes_sent_this_window = 0;
self.bytes_received_this_window = 0;
self.bandwidth_window_start = time;
true
} else {
false
}
}
pub fn record_bytes_sent(&mut self, bytes: u32) {
self.bytes_sent_this_window = self.bytes_sent_this_window.saturating_add(bytes);
}
pub fn record_bytes_received(&mut self, bytes: u32) {
self.bytes_received_this_window = self.bytes_received_this_window.saturating_add(bytes);
}
pub fn can_send_within_bandwidth(&self) -> bool {
if self.outgoing_bandwidth_limit == 0 {
return true; }
self.bytes_sent_this_window < self.outgoing_bandwidth_limit
}
pub fn bandwidth_utilization(&self) -> f32 {
if self.outgoing_bandwidth_limit == 0 {
return 0.0; }
self.bytes_sent_this_window as f32 / self.outgoing_bandwidth_limit as f32
}
pub fn can_receive_within_bandwidth(&self) -> bool {
if self.incoming_bandwidth_limit == 0 {
return true; }
self.bytes_received_this_window < self.incoming_bandwidth_limit
}
pub fn incoming_bandwidth_utilization(&self) -> f32 {
if self.incoming_bandwidth_limit == 0 {
return 0.0; }
self.bytes_received_this_window as f32 / self.incoming_bandwidth_limit as f32
}
pub fn set_outgoing_bandwidth_limit(&mut self, limit: u32) {
self.outgoing_bandwidth_limit = limit;
}
pub fn set_incoming_bandwidth_limit(&mut self, limit: u32) {
self.incoming_bandwidth_limit = limit;
}
pub fn outgoing_bandwidth_limit(&self) -> u32 {
self.outgoing_bandwidth_limit
}
pub fn incoming_bandwidth_limit(&self) -> u32 {
self.incoming_bandwidth_limit
}
pub fn bytes_sent_this_window(&self) -> u32 {
self.bytes_sent_this_window
}
pub fn bytes_received_this_window(&self) -> u32 {
self.bytes_received_this_window
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bandwidth_unlimited_by_default() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(0, 0, time);
assert!(throttle.can_send_within_bandwidth());
throttle.record_bytes_sent(1_000_000);
assert!(throttle.can_send_within_bandwidth());
assert_eq!(throttle.bandwidth_utilization(), 0.0);
}
#[test]
fn test_bandwidth_throttling_when_limited() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(1000, 0, time);
assert!(throttle.can_send_within_bandwidth());
assert_eq!(throttle.bandwidth_utilization(), 0.0);
throttle.record_bytes_sent(500);
assert!(throttle.can_send_within_bandwidth());
assert_eq!(throttle.bandwidth_utilization(), 0.5);
throttle.record_bytes_sent(500);
assert!(!throttle.can_send_within_bandwidth()); assert_eq!(throttle.bandwidth_utilization(), 1.0);
throttle.record_bytes_sent(100);
assert!(!throttle.can_send_within_bandwidth());
assert!(throttle.bandwidth_utilization() > 1.0);
let time_plus_1sec = time + Duration::from_secs(1);
throttle.update_bandwidth_window(time_plus_1sec);
assert!(throttle.can_send_within_bandwidth());
assert_eq!(throttle.bandwidth_utilization(), 0.0);
}
#[test]
fn test_bandwidth_limit_updates() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(0, 0, time);
assert_eq!(throttle.outgoing_bandwidth_limit(), 0);
assert_eq!(throttle.incoming_bandwidth_limit(), 0);
throttle.set_incoming_bandwidth_limit(1234);
throttle.set_outgoing_bandwidth_limit(5678);
assert_eq!(throttle.incoming_bandwidth_limit(), 1234);
assert_eq!(throttle.outgoing_bandwidth_limit(), 5678);
}
#[test]
fn test_incoming_bandwidth_throttling() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(0, 2000, time);
assert!(throttle.can_receive_within_bandwidth());
assert_eq!(throttle.incoming_bandwidth_utilization(), 0.0);
throttle.record_bytes_received(1000);
assert!(throttle.can_receive_within_bandwidth());
assert_eq!(throttle.incoming_bandwidth_utilization(), 0.5);
throttle.record_bytes_received(1000);
assert!(!throttle.can_receive_within_bandwidth()); assert_eq!(throttle.incoming_bandwidth_utilization(), 1.0);
let time_plus_1sec = time + Duration::from_secs(1);
throttle.update_bandwidth_window(time_plus_1sec);
assert!(throttle.can_receive_within_bandwidth());
assert_eq!(throttle.incoming_bandwidth_utilization(), 0.0);
}
#[test]
fn test_bytes_tracking() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(1000, 2000, time);
assert_eq!(throttle.bytes_sent_this_window(), 0);
assert_eq!(throttle.bytes_received_this_window(), 0);
throttle.record_bytes_sent(100);
throttle.record_bytes_received(200);
assert_eq!(throttle.bytes_sent_this_window(), 100);
assert_eq!(throttle.bytes_received_this_window(), 200);
let time_plus_1sec = time + Duration::from_secs(1);
throttle.update_bandwidth_window(time_plus_1sec);
assert_eq!(throttle.bytes_sent_this_window(), 0);
assert_eq!(throttle.bytes_received_this_window(), 0);
}
#[test]
fn test_saturating_addition() {
let time = Instant::now();
let mut throttle = BandwidthThrottle::new(1000, 2000, time);
throttle.record_bytes_sent(u32::MAX);
throttle.record_bytes_sent(100); assert_eq!(throttle.bytes_sent_this_window(), u32::MAX);
throttle.record_bytes_received(u32::MAX);
throttle.record_bytes_received(100); assert_eq!(throttle.bytes_received_this_window(), u32::MAX);
}
}