use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ResourceLimit {
pub max_scan_rows: u64,
pub max_execution_time: Duration,
max_memory_mb: u64,
}
impl Default for ResourceLimit {
fn default() -> Self {
Self {
max_scan_rows: 10_000_000,
max_execution_time: Duration::from_secs(300),
max_memory_mb: 4096,
}
}
}
impl ResourceLimit {
pub fn unlimited() -> Self {
Self {
max_scan_rows: u64::MAX,
max_execution_time: Duration::from_secs(u64::MAX),
max_memory_mb: u64::MAX,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceExceeded {
ScanRowsExceeded { actual: u64, limit: u64 },
ExecutionTimeExceeded { actual_ms: u64, limit_ms: u64 },
MemoryExceeded { actual_mb: u64, limit_mb: u64 },
}
#[derive(Debug, Clone)]
pub enum ResourceCheckResult {
Ok,
Exceeded(ResourceExceeded),
}
#[derive(Debug)]
pub struct OlapResourceGuard {
limit: ResourceLimit,
allow_unlimited: bool,
}
impl OlapResourceGuard {
pub fn new(limit: ResourceLimit) -> Self {
Self {
limit,
allow_unlimited: false,
}
}
pub fn with_defaults() -> Self {
Self::new(ResourceLimit::default())
}
pub fn allow_unlimited(mut self) -> Self {
self.allow_unlimited = true;
self
}
pub fn limit(&self) -> &ResourceLimit {
&self.limit
}
pub fn check(
&self,
scan_rows: u64,
execution_time: Duration,
memory_mb: u64,
) -> ResourceCheckResult {
if self.allow_unlimited {
return ResourceCheckResult::Ok;
}
if scan_rows > self.limit.max_scan_rows {
return ResourceCheckResult::Exceeded(ResourceExceeded::ScanRowsExceeded {
actual: scan_rows,
limit: self.limit.max_scan_rows,
});
}
if execution_time > self.limit.max_execution_time {
return ResourceCheckResult::Exceeded(ResourceExceeded::ExecutionTimeExceeded {
actual_ms: execution_time.as_millis() as u64,
limit_ms: self.limit.max_execution_time.as_millis() as u64,
});
}
if memory_mb > self.limit.max_memory_mb {
return ResourceCheckResult::Exceeded(ResourceExceeded::MemoryExceeded {
actual_mb: memory_mb,
limit_mb: self.limit.max_memory_mb,
});
}
ResourceCheckResult::Ok
}
pub fn pre_check_scan_rows(&self, estimated_rows: u64) -> ResourceCheckResult {
if self.allow_unlimited || estimated_rows <= self.limit.max_scan_rows {
ResourceCheckResult::Ok
} else {
ResourceCheckResult::Exceeded(ResourceExceeded::ScanRowsExceeded {
actual: estimated_rows,
limit: self.limit.max_scan_rows,
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn within_limits_passes() {
let guard = OlapResourceGuard::with_defaults();
let result = guard.check(1000, Duration::from_secs(10), 100);
assert!(matches!(result, ResourceCheckResult::Ok));
}
#[test]
fn scan_rows_exceeded() {
let guard = OlapResourceGuard::with_defaults();
let result = guard.check(20_000_000, Duration::from_secs(10), 100);
assert!(matches!(
result,
ResourceCheckResult::Exceeded(ResourceExceeded::ScanRowsExceeded { .. })
));
}
#[test]
fn execution_time_exceeded() {
let guard = OlapResourceGuard::with_defaults();
let result = guard.check(1000, Duration::from_secs(600), 100);
assert!(matches!(
result,
ResourceCheckResult::Exceeded(ResourceExceeded::ExecutionTimeExceeded { .. })
));
}
#[test]
fn unlimited_allows_everything() {
let guard = OlapResourceGuard::with_defaults().allow_unlimited();
let result = guard.check(u64::MAX, Duration::from_secs(u64::MAX), u64::MAX);
assert!(matches!(result, ResourceCheckResult::Ok));
}
#[test]
fn pre_check_scan_rows() {
let guard = OlapResourceGuard::with_defaults();
assert!(matches!(
guard.pre_check_scan_rows(5000),
ResourceCheckResult::Ok
));
assert!(matches!(
guard.pre_check_scan_rows(20_000_000),
ResourceCheckResult::Exceeded(_)
));
}
}