use std::{fmt::Debug, sync::Arc, time::Duration};
use crate::io::device::statistics::Statistics;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageFilterResult {
Admit,
Reject,
Throttled(Duration),
}
impl StorageFilterResult {
pub fn is_admitted(&self) -> bool {
matches!(self, StorageFilterResult::Admit)
}
pub fn is_rejected(&self) -> bool {
matches!(self, StorageFilterResult::Reject)
}
}
pub trait StorageFilterCondition: Send + Sync + Debug + 'static {
fn filter(&self, stats: &Arc<Statistics>, hash: u64, estimated_size: usize) -> StorageFilterResult;
}
#[derive(Debug, Default)]
pub struct StorageFilter {
conditions: Vec<Box<dyn StorageFilterCondition>>,
}
impl StorageFilter {
pub fn new() -> Self {
Self::default()
}
pub fn with_condition<C: StorageFilterCondition>(mut self, condition: C) -> Self {
self.conditions.push(Box::new(condition));
self
}
pub fn filter(&self, stats: &Arc<Statistics>, hash: u64, estimated_size: usize) -> StorageFilterResult {
let mut duration = Duration::ZERO;
for condition in &self.conditions {
match condition.filter(stats, hash, estimated_size) {
StorageFilterResult::Admit => {}
StorageFilterResult::Reject => return StorageFilterResult::Reject,
StorageFilterResult::Throttled(dur) => duration += dur,
}
}
if duration.is_zero() {
StorageFilterResult::Admit
} else {
StorageFilterResult::Throttled(duration)
}
}
}
pub mod conditions {
use std::ops::{Bound, Range, RangeBounds};
pub use super::*;
#[derive(Debug, Default)]
pub struct AdmitAll;
impl StorageFilterCondition for AdmitAll {
fn filter(&self, _: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
StorageFilterResult::Admit
}
}
#[derive(Debug, Default)]
pub struct RejectAll;
impl StorageFilterCondition for RejectAll {
fn filter(&self, _: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
StorageFilterResult::Reject
}
}
#[derive(Debug, Default)]
pub struct IoThrottle;
impl StorageFilterCondition for IoThrottle {
fn filter(&self, stats: &Arc<Statistics>, _: u64, _: usize) -> StorageFilterResult {
let duration = stats.write_throttle();
if duration.is_zero() {
StorageFilterResult::Admit
} else {
StorageFilterResult::Throttled(duration)
}
}
}
#[derive(Debug)]
pub struct EstimatedSize {
range: Range<usize>,
}
impl EstimatedSize {
pub fn new<R: RangeBounds<usize>>(range: R) -> Self {
let start = match range.start_bound() {
Bound::Included(v) => *v,
Bound::Excluded(v) => *v + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(v) => *v + 1,
Bound::Excluded(v) => *v,
Bound::Unbounded => usize::MAX,
};
Self { range: start..end }
}
}
impl StorageFilterCondition for EstimatedSize {
fn filter(&self, _: &Arc<Statistics>, _: u64, estimated_size: usize) -> StorageFilterResult {
if self.range.contains(&estimated_size) {
StorageFilterResult::Admit
} else {
StorageFilterResult::Reject
}
}
}
}
#[cfg(test)]
mod tests {
use super::conditions::*;
use crate::Throttle;
#[test]
fn test_estimated_size_condition() {
let condition = EstimatedSize::new(10..20);
let statistics = Arc::new(Statistics::new(Throttle::default()));
assert_eq!(condition.filter(&statistics, 0, 15), StorageFilterResult::Admit);
assert_eq!(condition.filter(&statistics, 0, 5), StorageFilterResult::Reject);
assert_eq!(condition.filter(&statistics, 0, 20), StorageFilterResult::Reject);
}
}