use crate::catalog::FileExecutionSettingsV1;
use crate::query::types::{ExecutionHints, ReadPlan, TetError};
use crate::utils::dtype::ElementDtype;
use crate::utils::host_memory;
pub const DEFAULT_MEMORY_BUDGET_BYTES: u64 = 256 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryStrategy {
StreamingFold,
CappedInMemory,
MmapSpill,
InMemoryMaterialize,
TempSpillMaterialize,
TransformRam,
TransformSpill,
}
impl MemoryStrategy {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::StreamingFold => "streaming_fold",
Self::CappedInMemory => "capped_in_memory",
Self::MmapSpill => "mmap_spill",
Self::InMemoryMaterialize => "in_memory_materialize",
Self::TempSpillMaterialize => "temp_spill_materialize",
Self::TransformRam => "transform_ram",
Self::TransformSpill => "transform_spill",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExecutionBudget {
pub memory_budget_bytes: u64,
pub host_available_ram_bytes: Option<u64>,
pub memory_budget_percent_bps: u16,
}
impl Default for ExecutionBudget {
fn default() -> Self {
Self::resolve(&FileExecutionSettingsV1::default_engine(), None)
}
}
impl ExecutionBudget {
#[must_use]
pub fn resolve(file: &FileExecutionSettingsV1, hints: Option<&ExecutionHints>) -> Self {
let host = host_memory::available_memory_bytes();
if let Some(bytes) = hints.and_then(|h| h.memory_budget_bytes) {
return Self {
memory_budget_bytes: bytes.max(1),
host_available_ram_bytes: host,
memory_budget_percent_bps: file.effective_percent_bps(),
};
}
if file.memory_budget_bytes > 0 {
return Self {
memory_budget_bytes: u64::from(file.memory_budget_bytes).max(1),
host_available_ram_bytes: host,
memory_budget_percent_bps: file.effective_percent_bps(),
};
}
let bps = hints
.and_then(|h| h.memory_budget_percent_bps)
.filter(|&p| p > 0)
.unwrap_or_else(|| file.effective_percent_bps());
let memory_budget_bytes = if let Some(avail) = host {
avail.saturating_mul(u64::from(bps)) / 10_000
} else {
DEFAULT_MEMORY_BUDGET_BYTES
};
Self {
memory_budget_bytes: memory_budget_bytes.max(1),
host_available_ram_bytes: host,
memory_budget_percent_bps: bps,
}
}
#[must_use]
pub fn from_hints(hints: Option<&ExecutionHints>) -> Self {
Self::resolve(&FileExecutionSettingsV1::default_engine(), hints)
}
pub fn logical_element_bytes(
&self,
dtype: ElementDtype,
element_count: usize,
) -> Result<u64, TetError> {
let count = u64::try_from(element_count)
.map_err(|_| TetError::Validation("logical element count overflow".into()))?;
dtype
.bytes_from_elem_count(count)
.ok_or_else(|| TetError::Validation("logical element byte size overflow".into()))
}
pub fn logical_f32_bytes(&self, element_count: usize) -> Result<u64, TetError> {
self.logical_element_bytes(ElementDtype::F32, element_count)
}
pub fn exceeds_budget(
&self,
dtype: ElementDtype,
element_count: usize,
) -> Result<bool, TetError> {
Ok(self.logical_element_bytes(dtype, element_count)? > self.memory_budget_bytes)
}
pub fn exceeds_budget_f32(&self, element_count: usize) -> Result<bool, TetError> {
self.exceeds_budget(ElementDtype::F32, element_count)
}
pub fn full_tensor_exceeds_budget(
&self,
plan: &ReadPlan,
dtype: ElementDtype,
) -> Result<bool, TetError> {
self.exceeds_budget(dtype, plan.logical_f32_element_count)
}
pub fn full_tensor_exceeds_budget_f32(&self, plan: &ReadPlan) -> Result<bool, TetError> {
self.full_tensor_exceeds_budget(plan, ElementDtype::F32)
}
}