Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 15 additions & 56 deletions crates/asap-aware-mapping/src/analytical_cost.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Analytical, resource-dimensional cost estimates.
//!
//! This module deliberately keeps CPU work, retained state, and scan I/O
//! separate. They become one planner objective only through an explicit
//! [`ResourceCalibration`]; without that calibration the dimensional
//! estimate is still useful for explanations, but is not silently comparable.
//! separate. Plan selection (Stage 3) turns them into one objective with
//! [`crate::stage3_plan_selection::ResourceUnitCost`].

use std::collections::{HashMap, HashSet};

Expand Down Expand Up @@ -148,42 +147,6 @@ pub fn cache_hit_ratios(
Ok((resolved.result_hit_ratio, resolved.buffer_hit_ratio))
}

/// Conversion from physical dimensions to one deployment-specific objective.
/// Memory's coefficient means cost units per retained byte over this model's
/// explicit comparison scope; it is not mixed with a rate implicitly.
///
/// The legacy core's prices ([`crate::physical_plan_cost_model`]); plan
/// selection (Stage 3) prices with
/// [`crate::stage3_plan_selection::ResourceUnitCost`] instead.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceCalibration {
pub cost_per_cpu_op: f64,
pub cost_per_scan_byte: f64,
pub cost_per_retained_byte: f64,
pub version: String,
}

impl ResourceCalibration {
pub fn validate(&self) -> Result<(), AnalyticalCostError> {
for (name, value) in [
("cost_per_cpu_op", self.cost_per_cpu_op),
("cost_per_scan_byte", self.cost_per_scan_byte),
("cost_per_retained_byte", self.cost_per_retained_byte),
] {
if !value.is_finite() || value < 0.0 {
return Err(AnalyticalCostError::InvalidCalibration(name, value));
}
}
if self.cost_per_cpu_op == 0.0
&& self.cost_per_scan_byte == 0.0
&& self.cost_per_retained_byte == 0.0
{
return Err(AnalyticalCostError::ZeroCalibration);
}
Ok(())
}
}

/// An analytical estimate requires CPU work, peak memory, and scanned bytes.
/// The shared resource container keeps other dimensions explicitly unknown.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -2142,23 +2105,6 @@ fn validate_promql_series_sample(
Ok(())
}

impl ResourceEstimate {
pub fn calibrated_cost(
self,
calibration: &ResourceCalibration,
) -> Result<f64, AnalyticalCostError> {
calibration.validate()?;
let value = self.cpu_ops() * calibration.cost_per_cpu_op
+ self.scan_bytes() as f64 * calibration.cost_per_scan_byte
+ self.peak_memory_bytes() as f64 * calibration.cost_per_retained_byte;
if value.is_finite() {
Ok(value)
} else {
Err(AnalyticalCostError::Overflow)
}
}
}

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum AnalyticalCostError {
#[error("data arrival {0:?} is unsupported by the selected analytical adapter")]
Expand Down Expand Up @@ -2219,6 +2165,19 @@ pub enum AnalyticalCostError {
},
#[error("invalid physical DAG: {0}")]
InvalidPhysicalDAG(&'static str),
/// Bad input: plan selection's measured table has two entries for one
/// sketch configuration and operation.
#[error("measured_operation_costs has two entries for {operation:?} on {sketch:?}")]
DuplicateMeasuredOperationCost {
sketch: SketchParams,
operation: crate::stage3_plan_selection::SummaryOperation,
},
/// Bad input: a non-empty measured table with `ms_per_cpu_op` not
/// positive, so a measured time cannot be counted in CPU operations.
#[error(
"measured_operation_costs needs a positive ms_per_cpu_op to count measured times as operations, got {0}"
)]
MeasuredCostsWithoutCpuSpeed(f64),
}

fn checked_bytes(parts: &[u64]) -> Result<u64, AnalyticalCostError> {
Expand Down
Loading
Loading