use std::time::{Duration, Instant};
struct LeakyBucket {
_last_n: Vec<Instant>,
_max: usize,
}
impl LeakyBucket {
pub fn new(max: usize) -> Self {
Self {
_last_n: Vec::with_capacity(max),
_max: max,
}
}
pub fn insert(&mut self, now: Instant) {
if self._last_n.len() == self._max {
self._last_n.remove(0);
}
self._last_n.push(now);
}
pub fn len(&self) -> usize {
self._last_n.len()
}
pub fn items(&self) -> &Vec<Instant> {
&self._last_n
}
}
pub struct Chug {
_bucket: LeakyBucket,
_current_work: usize,
_total_work: usize,
}
impl Chug {
pub fn new(max: usize, total_work: usize) -> Self {
Self {
_bucket: LeakyBucket::new(max),
_current_work: 0,
_total_work: total_work,
}
}
pub fn tick(&mut self) {
let now = Instant::now();
self._current_work += 1;
self._bucket.insert(now);
}
pub fn eta(&self) -> Option<Duration> {
if self._bucket.len() < 2 {
return None;
}
let time_between = {
let items = self._bucket.items().clone();
let mut time_between = Vec::with_capacity(items.len() - 1);
for i in 0..items.len() - 1 {
time_between.push(items[i + 1].duration_since(items[i]));
}
time_between
};
let median_between = {
let mut time_between = time_between;
time_between.sort();
let mid = time_between.len() / 2;
time_between[mid].as_millis() as u64
};
if self._current_work > self._total_work {
return None;
}
let remaining = self._total_work - self._current_work;
if remaining == 0 {
None
} else {
let eta = median_between * remaining as u64;
Some(std::time::Duration::from_millis(eta))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_leaky_bucket() {
let mut bucket = LeakyBucket::new(10);
assert_eq!(bucket.len(), 0);
for i in 0..10 {
bucket.insert(Instant::now());
assert_eq!(bucket.len(), i + 1);
}
for _ in 0..10 {
bucket.insert(Instant::now());
assert_eq!(bucket.len(), 10);
}
}
#[test]
fn test_empty() {
let chug = Chug::new(10, 100);
assert_eq!(chug.eta(), None);
}
#[test]
fn test_completed() {
let mut chug = Chug::new(10, 100);
for _ in 0..100 {
chug.tick();
std::thread::sleep(std::time::Duration::from_millis(10));
match chug.eta() {
Some(eta) => {
println!("ETA: {}", eta.as_secs());
}
None => {
println!("ETA: None");
}
}
}
assert_eq!(chug.eta(), None);
}
#[test]
fn test_smaller_than_max() {
let mut chug = Chug::new(10, 100);
for _ in 0..4 {
chug.tick();
}
assert!(chug.eta().is_some())
}
#[test]
fn test_larger_than_max() {
let mut chug = Chug::new(10, 100);
for _ in 0..30 {
chug.tick();
}
assert!(chug.eta().is_some())
}
#[test]
fn test_just_under_max() {
let mut chug = Chug::new(10, 100);
for _ in 0..9 {
chug.tick();
}
assert!(chug.eta().is_some())
}
#[test]
fn test_just_over_total() {
let mut chug = Chug::new(10, 100);
for _ in 0..200 {
chug.tick();
}
assert!(chug.eta().is_none())
}
#[test]
fn test_just_under_total() {
let mut chug = Chug::new(10, 100);
for _ in 0..99 {
chug.tick();
}
assert!(chug.eta().is_some())
}
}