From 91e519aa20b468b1b861864091c2b7e1b91042d1 Mon Sep 17 00:00:00 2001 From: Zeying Zhu <50204836+zzylol@users.noreply.github.com> Date: Sat, 17 Oct 2026 20:43:43 +0000 Subject: [PATCH 1/2] refactor!: name accuracy, rejection and CLI fields by what they hold - AccuracyTarget.error is max_error (JSON key "max_error"), matching the AccuracyTarget::max_error builder. AccuracySpec.error, the guarantee's bound expression, keeps its name. - Rejection.valid is replaced by cause: RejectionCause, which says why Stage 3 did not select the candidate (a failed check or a loss under the cost model); stage_pipeline writes it as "cause" and the viewer validates it. - stage_pipeline's --epsilon/--delta/--metric are --max-error, --failure-probability and --accuracy-metric; sketch_coverage's --epsilon is --max-error; the viewer's /api/plan payload keys follow. - PlanOutput::execution_timed_dag is physical_asap_dag, and realization::accuracy_target is sizing_target. - Stage 3's node docs say a node is an operator node of the physical plan DAG, keyed by PhysicalASAPNodeId, not a machine. Regenerate the Example 1 viewer fixture for the new keys. --- .../asap-aware-mapping/src/accuracy/budget.rs | 12 +- crates/asap-aware-mapping/src/accuracy/mod.rs | 2 +- crates/asap-aware-mapping/src/pass/mod.rs | 13 +- .../src/per_computation_candidates.rs | 8 +- crates/asap-aware-mapping/src/realization.rs | 6 +- .../src/stage3_plan_selection.rs | 225 +++++++++++---- .../measured_operation_costs.rs | 13 +- crates/devtools/src/bin/sketch_coverage.rs | 18 +- crates/devtools/src/bin/stage_pipeline.rs | 57 ++-- crates/devtools/tests/stage_pipeline.rs | 35 ++- .../tests/promql_conformance.rs | 2 +- .../frontend-promql/tests/promql_lowering.rs | 2 +- crates/frontend-sql/tests/sql_lowering.rs | 2 +- .../tests/operator_design_examples.rs | 2 +- .../tests/planner_layering_common/mod.rs | 2 +- .../tests/planner_layering_example4.rs | 5 +- .../tests/promql_to_post_asap.rs | 2 +- crates/planner/tests/summary_sharing.rs | 11 +- crates/types/src/pre_asap/agg_intent.rs | 14 +- crates/types/src/types.rs | 50 ++-- crates/types/src/workload.rs | 12 +- .../architecture/input-output-workflow.md | 2 +- ...d_interface_with_pluggable_optimization.md | 2 +- .../physical-planning-and-deployment.md | 4 +- .../asap-aware-mapping-contracts.md | 9 +- .../develop_docs/extend-asap-aware-mapping.md | 3 +- docs/develop_docs/library-api.md | 10 +- docs/user_guide_docs/run-a-query.md | 10 +- docs/user_guide_docs/stage-viewer.md | 6 +- tools/dag-viewer/README.md | 10 +- tools/dag-viewer/editor.js | 4 +- .../examples/planner-layering-example1.json | 272 +++++++++--------- tools/dag-viewer/index.html | 4 +- tools/dag-viewer/server.py | 28 +- tools/dag-viewer/stages.js | 18 +- tools/dag-viewer/test_server.py | 30 +- tools/dag-viewer/test_viewer.py | 20 +- 37 files changed, 540 insertions(+), 385 deletions(-) diff --git a/crates/asap-aware-mapping/src/accuracy/budget.rs b/crates/asap-aware-mapping/src/accuracy/budget.rs index da9b70cb..af2c78ea 100644 --- a/crates/asap-aware-mapping/src/accuracy/budget.rs +++ b/crates/asap-aware-mapping/src/accuracy/budget.rs @@ -19,16 +19,16 @@ pub fn per_input_target( metric: AccuracyMetric, approximate_inputs: usize, ) -> Option { - if target.is_exact() || !(target.error.is_finite() && target.error > 0.0) { + if target.is_exact() || !(target.max_error.is_finite() && target.max_error > 0.0) { return None; } let fraction = 1.0 / approximate_inputs.max(1) as f64; - let error = match metric { - AccuracyMetric::Relative => (1.0 + target.error).powf(fraction) - 1.0, - _ => target.error * fraction, + let max_error = match metric { + AccuracyMetric::Relative => (1.0 + target.max_error).powf(fraction) - 1.0, + _ => target.max_error * fraction, }; Some(AccuracyTarget { - error, + max_error, failure_probability: target.failure_probability.map(|delta| delta * fraction), ..target.clone() }) @@ -70,7 +70,7 @@ mod tests { 2, ) .unwrap(); - assert!((share.error - 0.1).abs() < 1e-12); + assert!((share.max_error - 0.1).abs() < 1e-12); } /// No approximate input meets an exact target. diff --git a/crates/asap-aware-mapping/src/accuracy/mod.rs b/crates/asap-aware-mapping/src/accuracy/mod.rs index 0a517730..fda3b0b5 100644 --- a/crates/asap-aware-mapping/src/accuracy/mod.rs +++ b/crates/asap-aware-mapping/src/accuracy/mod.rs @@ -110,7 +110,7 @@ impl AccuracyModel for DefaultAccuracyModel { .accuracy_metric .is_none_or(|metric| metric_meets(guarantee.spec.accuracy_metric, metric)) && scope_meets(guarantee, target) - && within(guarantee.spec.error.evaluate(), target.error) + && within(guarantee.spec.error.evaluate(), target.max_error) && target .failure_probability .is_none_or(|delta| within(guarantee.spec.failure_probability.evaluate(), delta)) diff --git a/crates/asap-aware-mapping/src/pass/mod.rs b/crates/asap-aware-mapping/src/pass/mod.rs index e32e3bfd..25b3929c 100644 --- a/crates/asap-aware-mapping/src/pass/mod.rs +++ b/crates/asap-aware-mapping/src/pass/mod.rs @@ -262,8 +262,9 @@ impl PlanOutput { nodes } - /// The workload as one physical ASAP DAG, with shared sub-DAGs exported - /// once: operator queries become its `roots` and standalone scalar + /// The workload as one physical ASAP DAG, every node computed at query + /// time (the default [`MaterializationAssignment`]), with shared + /// sub-DAGs exported once: operator queries become its `roots` and standalone scalar /// queries its `scalar_roots`, keyed by position in the batch. That /// position is the entry index, because `check_contract` requires dense /// entry indices. @@ -272,7 +273,7 @@ impl PlanOutput { /// ([`PhysicalASAPDAGValidationError::NoRoots`](asap_types::ir::physical_export::PhysicalASAPDAGValidationError::NoRoots)). /// A workload of only literal scalars yields a DAG with no nodes: just /// its `scalar_roots`. - pub fn execution_timed_dag( + pub fn physical_asap_dag( &self, ) -> Result { // One memo, so a node several roots share by `Rc` is timed and exported once. @@ -617,18 +618,18 @@ mod tests { /// `InvalidPhysicalDAG`. A workload of only literal scalars exports a DAG /// with no nodes, just its `scalar_roots`. #[test] - fn execution_timed_dag_of_an_empty_and_a_literal_only_workload() { + fn physical_asap_dag_of_an_empty_and_a_literal_only_workload() { use asap_types::ir::physical_export::PhysicalASAPDAGValidationError; use asap_types::ir::ScalarExpr; assert_eq!( - PlanOutput::new(Vec::new()).execution_timed_dag().err(), + PlanOutput::new(Vec::new()).physical_asap_dag().err(), Some(ExecutionDataStateError::InvalidPhysicalDAG( PhysicalASAPDAGValidationError::NoRoots )) ); let mut literal = PlanOutput::new(Vec::new()); literal.scalar_roots = vec![(0, ScalarExpr::literal_f64(1.0))]; - let dag = literal.execution_timed_dag().unwrap(); + let dag = literal.physical_asap_dag().unwrap(); assert!(dag.nodes.is_empty() && dag.roots.is_empty()); let [(0, ScalarExpr::Literal(_))] = dag.scalar_roots.as_slice() else { panic!("one literal scalar root: {:?}", dag.scalar_roots) diff --git a/crates/asap-aware-mapping/src/per_computation_candidates.rs b/crates/asap-aware-mapping/src/per_computation_candidates.rs index d95d30b9..37f42e62 100644 --- a/crates/asap-aware-mapping/src/per_computation_candidates.rs +++ b/crates/asap-aware-mapping/src/per_computation_candidates.rs @@ -121,9 +121,7 @@ use crate::accuracy::estimators::{hll, univmon}; use crate::accuracy::{per_input_target, AccuracyModel, DefaultAccuracyModel, PropagationStats}; use crate::deployment_capabilities::DeploymentCapabilities; use crate::function_rules::approximate_input_composition; -use crate::realization::{ - accuracy_budget, accuracy_target, default_size_params, summary_candidates, -}; +use crate::realization::{accuracy_budget, default_size_params, sizing_target, summary_candidates}; /// How one Pass 1 target is computed: exact or a summary. /// @@ -456,7 +454,7 @@ pub fn computation_candidates_for_intent( candidates: vec![exact], pruned: vec![], }; - if let Some(target) = accuracy_target(intent) { + if let Some(target) = sizing_target(intent) { if !target.is_exact() { let (epsilon, delta) = accuracy_budget(&target); let unit = |value: f64| value > 0.0 && value < 1.0; @@ -3028,7 +3026,7 @@ impl Sizing { /// requirement ([`per_input_target`]), split in the metric of the summary's own /// bound: a relative error splits multiplicatively. fn sized(algorithm: &SketchAlgorithm, intent: &AggIntent, share: usize) -> SketchKind { - let target = accuracy_target(intent).unwrap_or_else(AccuracyTarget::exact); + let target = sizing_target(intent).unwrap_or_else(AccuracyTarget::exact); let size = |target: &AccuracyTarget| { let (epsilon, delta) = accuracy_budget(target); SketchKind::new( diff --git a/crates/asap-aware-mapping/src/realization.rs b/crates/asap-aware-mapping/src/realization.rs index e7fb4f81..a3060af1 100644 --- a/crates/asap-aware-mapping/src/realization.rs +++ b/crates/asap-aware-mapping/src/realization.rs @@ -63,13 +63,13 @@ pub fn summary_candidates(intent: &AggIntent) -> &'static [SketchAlgorithm] { /// The [`AccuracyTarget`] an approximate-capable intent's summary is sized /// and checked against, or `None` for every other intent (no sketch -/// candidate applies). It is the query's target restated in the intent's own metric +/// candidate applies): the query's target restated in the intent's own metric /// (#690 §4): an L1-scaled absolute error for counts and top-k frequencies, /// a relative error for distinct counts and L2 frequency moments, an /// absolute error for entropy, and for a quantile the query's metric or a /// rank error when none is set. The query's own metric, set or not, is /// checked at its root in Stage 3. -pub fn accuracy_target(intent: &AggIntent) -> Option { +pub fn sizing_target(intent: &AggIntent) -> Option { let (target, metric) = match intent { AggIntent::Quantile { accuracy, .. } => ( accuracy, @@ -107,7 +107,7 @@ pub fn accuracy_budget(accuracy: &AccuracyTarget) -> (f64, f64) { return (f64::MIN_POSITIVE, DEFAULT_DELTA); } ( - accuracy.error, + accuracy.max_error, accuracy.failure_probability.unwrap_or(DEFAULT_DELTA), ) } diff --git a/crates/asap-aware-mapping/src/stage3_plan_selection.rs b/crates/asap-aware-mapping/src/stage3_plan_selection.rs index b2e3a9c8..c3999593 100644 --- a/crates/asap-aware-mapping/src/stage3_plan_selection.rs +++ b/crates/asap-aware-mapping/src/stage3_plan_selection.rs @@ -24,9 +24,11 @@ //! ## Cost model: cost per second of wall time //! //! A candidate's cost is what running it costs per second ([`COST_UNIT`]), -//! so queries with different recurrences add up. Each node's price for one +//! so queries with different recurrences add up. A node here is one +//! operator node of the candidate's physical plan DAG (`PhysicalASAPDAG`), +//! not a machine. Each node's price for one //! computation is multiplied by how often it is computed per second -//! ([`NodeCost::per_second`]): +//! ([`NodeCost::cost_per_second`]): //! //! * A `NotMaterialized` node is recomputed for each consumer that reads it //! and is not stored: it is charged once per consumer @@ -450,7 +452,10 @@ impl PlanSelectionSettings { } } -/// One node's price per second of wall time, with how it was measured. +/// One node's price per second of wall time, with how it was measured. A +/// node is one operator node of the candidate's physical plan DAG +/// (`PhysicalASAPDAG`), keyed by its [`PhysicalASAPNodeId`] in +/// [`CandidateCost::per_node`]; not a machine. #[derive(Debug, Clone, PartialEq)] pub struct NodeCost { /// Its price per second under cost model 1; `None` under cost models 2 @@ -469,7 +474,8 @@ pub struct NodeCost { } /// One candidate's usage and price over the whole workload: `usage` is -/// the whole candidate's, `per_node` one price per DAG node. +/// the whole candidate's, `per_node` one price per operator node of its +/// physical plan DAG. #[derive(Debug, Clone, PartialEq)] pub struct CandidateCost { pub usage: ResourceUsage, @@ -481,6 +487,8 @@ pub struct CandidateCost { /// [`COST_SOURCE`] (all formulas), [`MEASURED_COST_SOURCE`] (all /// measured) or [`MIXED_COST_SOURCE`]. pub source: &'static str, + /// Each operator node of the candidate's physical plan DAG, by its id + /// in that DAG, with its price. pub per_node: BTreeMap, } @@ -536,18 +544,70 @@ pub(crate) fn accuracy_targets(entries: &[QueryWorkloadEntry]) -> Vec bool { + matches!( + self, + Self::FailureProbabilityResizeFailed + | Self::AccuracyTargetMissed + | Self::RawDataNotRetained + | Self::LatencyBoundExceeded + | Self::OverClusterCapacity + ) + } +} + /// The accuracy Stage 3 accepted for one query of a valid candidate: the /// metric the query's target set (if any) and the metric of the guarantee /// that met it. With no metric set, any metric meeting the target's ε, δ @@ -680,7 +740,7 @@ pub fn select_plan( Err(reason) => { rejected.push(Rejection { id: candidate.id.clone(), - valid: false, + cause: RejectionCause::FailureProbabilityResizeFailed, reason, }); continue; @@ -691,7 +751,7 @@ pub fn select_plan( Err(reason) => { rejected.push(Rejection { id: candidate.id.clone(), - valid: false, + cause: RejectionCause::AccuracyTargetMissed, reason, }); continue; @@ -704,7 +764,7 @@ pub fn select_plan( if let Some(reason) = raw_data_violation(&candidate.dag, &nodes, &kept_chains) { rejected.push(Rejection { id: candidate.id.clone(), - valid: false, + cause: RejectionCause::RawDataNotRetained, reason, }); continue; @@ -723,14 +783,17 @@ pub fn select_plan( node, error, })?; - let violation = latency_violation(&cost, entries).or_else(|| match &settings.cost_model { - CostModel::ClusterCapacity(limits) => capacity_violation(&cost.usage, limits), - _ => None, - }); - if let Some(reason) = violation { + let violation = latency_violation(&cost, entries) + .map(|reason| (RejectionCause::LatencyBoundExceeded, reason)) + .or_else(|| match &settings.cost_model { + CostModel::ClusterCapacity(limits) => capacity_violation(&cost.usage, limits) + .map(|reason| (RejectionCause::OverClusterCapacity, reason)), + _ => None, + }); + if let Some((cause, reason)) = violation { rejected.push(Rejection { id: candidate.id.clone(), - valid: false, + cause, reason, }); continue; @@ -753,10 +816,11 @@ pub fn select_plan( for (index, _) in &valid { let id = id(*index); if id != selected { + let (cause, reason) = ranking.lost_reason(id, &selected, &costs, &front); rejected.push(Rejection { id: id.to_string(), - valid: true, - reason: ranking.lost_reason(id, &selected, &costs, &front), + cause, + reason, }); } } @@ -913,14 +977,15 @@ impl Ranking { order == Ordering::Less } - /// Why valid candidate `id` was not `selected`. + /// Why valid candidate `id` was not `selected`, and the reason with + /// its numbers. fn lost_reason( self, id: &str, selected: &str, costs: &BTreeMap, front: &[String], - ) -> String { + ) -> (RejectionCause, String) { let (cost, best) = (&costs[id], &costs[selected]); if !self.better(best, cost) { let tied = match self { @@ -930,40 +995,58 @@ impl Ranking { ), _ => describe_usage(&cost.usage), }; - return format!( - "tied with the selected candidate {selected} at {tied}; lost on enumeration order" + return ( + RejectionCause::TiedLostOnEnumerationOrder, + format!( + "tied with the selected candidate {selected} at {tied}; lost on enumeration order" + ), ); } match self { - Ranking::Price => format!( - "costlier: {:.3} vs {:.3} {COST_UNIT}", - cost.cost_per_second.unwrap_or_default(), - best.cost_per_second.unwrap_or_default() + Ranking::Price => ( + RejectionCause::Costlier, + format!( + "costlier: {:.3} vs {:.3} {COST_UNIT}", + cost.cost_per_second.unwrap_or_default(), + best.cost_per_second.unwrap_or_default() + ), ), - Ranking::Latency => format!( - "slower: {} vs {}", - describe_usage(&cost.usage), - describe_usage(&best.usage) + Ranking::Latency => ( + RejectionCause::Slower, + format!( + "slower: {} vs {}", + describe_usage(&cost.usage), + describe_usage(&best.usage) + ), ), - Ranking::Memory => format!( - "more memory: {} vs {}", - describe_usage(&cost.usage), - describe_usage(&best.usage) + Ranking::Memory => ( + RejectionCause::MoreMemory, + format!( + "more memory: {} vs {}", + describe_usage(&cost.usage), + describe_usage(&best.usage) + ), ), - Ranking::Front if front.iter().any(|f| f == id) => format!( - "on the Pareto front, not its lowest-latency member: {} vs {}", - describe_usage(&cost.usage), - describe_usage(&best.usage) + Ranking::Front if front.iter().any(|f| f == id) => ( + RejectionCause::NotLowestLatencyOnParetoFront, + format!( + "on the Pareto front, not its lowest-latency member: {} vs {}", + describe_usage(&cost.usage), + describe_usage(&best.usage) + ), ), Ranking::Front => { let by = front .iter() .find(|f| dominates(&costs[f.as_str()].usage, &cost.usage)) .map_or(selected, String::as_str); - format!( - "dominated by {by}: {} vs {}", - describe_usage(&cost.usage), - describe_usage(&costs[by].usage) + ( + RejectionCause::Dominated, + format!( + "dominated by {by}: {} vs {}", + describe_usage(&cost.usage), + describe_usage(&costs[by].usage) + ), ) } } @@ -3174,7 +3257,7 @@ mod tests { assert!(!summaries.contains(&selection.selected.as_str())); for id in &summaries { let rejection = selection.rejected.iter().find(|r| r.id == *id).unwrap(); - assert!(!rejection.valid); + assert_eq!(rejection.cause, RejectionCause::AccuracyTargetMissed); assert!( rejection.reason.contains("misses target"), "{}", @@ -3216,7 +3299,7 @@ mod tests { ) .unwrap(); let p2 = selection.rejected.iter().find(|r| r.id == "P2").unwrap(); - assert!(!p2.valid); + assert_eq!(p2.cause, RejectionCause::AccuracyTargetMissed); assert!(p2.reason.contains("non-negative"), "{}", p2.reason); } @@ -3249,7 +3332,7 @@ mod tests { ) .unwrap(); let p2 = selection.rejected.iter().find(|r| r.id == "P2").unwrap(); - assert!(!p2.valid); + assert_eq!(p2.cause, RejectionCause::AccuracyTargetMissed); assert_eq!( !p2.reason.contains("non-negative"), admitted, @@ -3478,7 +3561,11 @@ mod tests { assert_eq!(cost.cost_per_second.unwrap(), sum); assert!(cost.cost_per_second.unwrap() > 0.0); } - let costlier: Vec<_> = selection.rejected.iter().filter(|r| r.valid).collect(); + let costlier: Vec<_> = selection + .rejected + .iter() + .filter(|r| r.cause == RejectionCause::Costlier) + .collect(); assert_eq!(costlier.len(), 1); assert_ne!(costlier[0].id, selection.selected); } @@ -3644,7 +3731,10 @@ mod tests { .iter() .find(|r| r.id == candidates[broken].id) .unwrap(); - assert!(!rejection.valid); + assert_eq!( + rejection.cause, + RejectionCause::FailureProbabilityResizeFailed + ); assert!( rejection .reason @@ -3998,7 +4088,11 @@ mod tests { }; let relative = select(AccuracyMetric::Relative); assert_eq!(relative.selected, "P1"); - let mut invalid: Vec<_> = relative.rejected.iter().filter(|r| !r.valid).collect(); + let mut invalid: Vec<_> = relative + .rejected + .iter() + .filter(|r| r.cause.failed_a_check()) + .collect(); invalid.sort_by(|a, b| a.id.cmp(&b.id)); assert_eq!( invalid.iter().map(|r| r.id.as_str()).collect::>(), @@ -4012,7 +4106,11 @@ mod tests { ); } let l1 = select(AccuracyMetric::ABSOLUTE_STREAM_L1); - assert!(l1.rejected.iter().all(|r| r.valid), "{:?}", l1.rejected); + assert!( + l1.rejected.iter().all(|r| !r.cause.failed_a_check()), + "{:?}", + l1.rejected + ); } /// A difference of two quantile estimates has no output guarantee, though @@ -4045,7 +4143,7 @@ mod tests { .unwrap(); for id in &summaries { let rejection = builtin.rejected.iter().find(|r| r.id == *id).unwrap(); - assert!(!rejection.valid); + assert_eq!(rejection.cause, RejectionCause::AccuracyTargetMissed); assert!( rejection.reason.contains("no guarantee"), "{}", @@ -5568,29 +5666,32 @@ mod tests { .unwrap() }; let retained = select(&keeping_query_time_results()); - assert!(retained.rejected.iter().all(|r| r.valid), "{retained:?}"); + assert!( + retained.rejected.iter().all(|r| !r.cause.failed_a_check()), + "{retained:?}" + ); let dropped = select(&DeploymentCapabilities { raw_data_retained: false, ..keeping_query_time_results() }); assert_eq!(dropped.selected, candidates[1].id); - let reasons: BTreeMap<&str, (bool, &str)> = dropped + let reasons: BTreeMap<&str, (RejectionCause, &str)> = dropped .rejected .iter() - .map(|r| (r.id.as_str(), (r.valid, r.reason.as_str()))) + .map(|r| (r.id.as_str(), (r.cause, r.reason.as_str()))) .collect(); let unretained = "at query time, but the deployment does not retain raw data"; assert_eq!( reasons[candidates[0].id.as_str()], ( - false, + RejectionCause::RawDataNotRetained, format!("scans 300000 ms of raw m {unretained}").as_str() ) ); assert_eq!( reasons[candidates[2].id.as_str()], ( - false, + RejectionCause::RawDataNotRetained, format!("scans 60000 ms of raw m {unretained}").as_str() ) ); @@ -5777,7 +5878,7 @@ mod tests { assert_eq!(selection.selected, candidates[1].id); let rejected = &selection.rejected[0]; assert_eq!(rejected.id, candidates[0].id); - assert!(!rejected.valid); + assert_eq!(rejected.cause, RejectionCause::LatencyBoundExceeded); assert!( rejected.reason.starts_with(&format!( "q1: latency {:.1} ms per evaluation (compaction 0.0 ms + query", @@ -6009,7 +6110,7 @@ mod tests { .iter() .find(|r| r.id == candidates[1].id) .unwrap(); - assert!(!b1.valid); + assert_eq!(b1.cause, RejectionCause::OverClusterCapacity); assert_eq!(b1.reason, "2 CPU cores at peak is over the cluster's 1"); } @@ -6093,7 +6194,7 @@ mod tests { .iter() .find(|r| r.id == candidates[0].id) .unwrap(); - assert!(!b2.valid); + assert_eq!(b2.cause, RejectionCause::OverClusterCapacity); assert_eq!( b2.reason, format!("disk {raw} bytes is over the cluster's {} bytes", raw - 1) @@ -6184,7 +6285,7 @@ mod tests { assert_eq!(selection.selected, candidates[0].id, "{objective:?}"); let twin = &selection.rejected[0]; assert_eq!(twin.id, candidates[1].id); - assert!(twin.valid); + assert_eq!(twin.cause, RejectionCause::TiedLostOnEnumerationOrder); assert!( twin.reason.starts_with(&format!( "tied with the selected candidate {}", diff --git a/crates/asap-aware-mapping/src/stage3_plan_selection/measured_operation_costs.rs b/crates/asap-aware-mapping/src/stage3_plan_selection/measured_operation_costs.rs index b5abeba2..1af6491f 100644 --- a/crates/asap-aware-mapping/src/stage3_plan_selection/measured_operation_costs.rs +++ b/crates/asap-aware-mapping/src/stage3_plan_selection/measured_operation_costs.rs @@ -5,9 +5,10 @@ //! with given parameters costs (e.g. sketch-bench's `rqe_atomic_costs.json`, //! converted by `asap-devtools`' `convert_rqe_atomic_costs`) and puts the //! table in [`PlanSelectionSettings::measured_operation_costs`]. Stage 3 -//! prices a sketch node's CPU from the entry for its sketch parameters and -//! operation when there is one, and from its formulas otherwise, and -//! reports which per node ([`NodeCost::price_source`]). The planner reads +//! prices a sketch node (an operator node of the physical plan DAG) from +//! the entry for its sketch parameters and operation when there is one, and +//! from its formulas otherwise, and reports which per node +//! ([`NodeCost::price_source`]). The planner reads //! only this table, never a benchmark file. //! //! A measured time enters [`ResourceUsage`] as CPU operations: `t` ns is @@ -51,7 +52,11 @@ pub struct MeasuredOperationCost { pub cpu_ns_per_operation: f64, } -/// Which prices one node's CPU operations came from. +/// Which prices one node's CPU operations came from. A node is one operator +/// node of the candidate's physical plan DAG (`PhysicalASAPDAG`), keyed by +/// its `PhysicalASAPNodeId` in [`CandidateCost::per_node`]; not a machine. +/// +/// [`CandidateCost::per_node`]: super::CandidateCost::per_node #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum NodePriceSource { diff --git a/crates/devtools/src/bin/sketch_coverage.rs b/crates/devtools/src/bin/sketch_coverage.rs index db9e0d61..362ca993 100644 --- a/crates/devtools/src/bin/sketch_coverage.rs +++ b/crates/devtools/src/bin/sketch_coverage.rs @@ -1,4 +1,4 @@ -// cargo run -p asap-devtools --bin sketch_coverage -- --sample-interval-ms [--epsilon ] +// cargo run -p asap-devtools --bin sketch_coverage -- --sample-interval-ms [--max-error ] // // Lowers every query in every corpus we have (mirrors `variant_coverage`'s // corpus list exactly, so the two reports are directly comparable) with an @@ -9,7 +9,7 @@ // // `--sample-interval-ms ` is the sample interval declared for every // metric a query reads, which its bare instant selectors look back. -// `--epsilon ` (default 0.01) sets the `AccuracyTarget` every query in +// `--max-error ` (ε, default 0.01) sets the `AccuracyTarget` every query in // every corpus lowers with. Without an approximate target, Pass 1 offers no // summary candidate. @@ -190,14 +190,14 @@ fn report(r: &CorpusCoverage) { println!(); } -fn parse_epsilon() -> f64 { +fn parse_max_error() -> f64 { let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { - if arg == "--epsilon" { - let raw = args.next().expect("--epsilon requires a value"); + if arg == "--max-error" { + let raw = args.next().expect("--max-error requires a value"); return raw .parse() - .unwrap_or_else(|_| panic!("--epsilon must be a float, got {raw:?}")); + .unwrap_or_else(|_| panic!("--max-error must be a float, got {raw:?}")); } } 0.01 @@ -219,9 +219,9 @@ fn parse_sample_interval() -> u64 { #[tokio::main] async fn main() { - let epsilon = parse_epsilon(); + let max_error = parse_max_error(); let interval_ms = parse_sample_interval(); - let accuracy = AccuracyTarget::max_error(epsilon); + let accuracy = AccuracyTarget::max_error(max_error); let mut results = Vec::new(); // ── PromQL corpora — same set as `variant_coverage` ── @@ -305,7 +305,7 @@ async fn main() { let total_rejected: usize = results.iter().map(|r| r.rejected).sum(); let total_covered: usize = results.iter().map(covered).sum(); - println!("=== global (epsilon = {epsilon}) ==="); + println!("=== global (max_error = {max_error}) ==="); println!( "total lowered: {total_lowered}, total failed: {total_failed}, rejected by Pass 1: {total_rejected}" ); diff --git a/crates/devtools/src/bin/stage_pipeline.rs b/crates/devtools/src/bin/stage_pipeline.rs index 64482a28..f6fb63f9 100644 --- a/crates/devtools/src/bin/stage_pipeline.rs +++ b/crates/devtools/src/bin/stage_pipeline.rs @@ -4,7 +4,7 @@ // planner-layering-3a and planner-layering-3b: #509 Example 3, Patterns A // and B; 3a has 972 candidates, all priced, 64 shown by default) // cargo run -p asap-devtools --bin stage_pipeline -- \ -// --promql "topk by (job) (10, rate(x[1m]))" --epsilon 0.01 --delta 0.001 --out run.json +// --promql "topk by (job) (10, rate(x[1m]))" --max-error 0.01 --failure-probability 0.001 --out run.json // cargo run -p asap-devtools --bin stage_pipeline -- \ // --table '{"name": "metrics", "columns": [{"name": "ts", "type": "timestamp"}, // {"name": "service", "type": "utf8"}], "time_index": 0}' \ @@ -32,8 +32,13 @@ // the shown candidates' costs and accepted accuracy (per query: the // target's metric or null when not set, the accepted guarantee's // metric or null when exact, the sketches read and a one-line -// summary), and every other shown candidate as -// rejected (`valid: false`) or costlier. Each node's `cost` is one +// summary), and every other shown candidate under `rejected` with +// its `cause`: a failed check (`failure_probability_resize_failed`, +// `accuracy_target_missed`, `raw_data_not_retained`, +// `latency_bound_exceeded`, `over_cluster_capacity`) or a loss under +// the cost model (`costlier`, `slower`, `more_memory`, +// `not_lowest_latency_on_pareto_front`, `dominated`, +// `tied_lost_on_enumeration_order`). Each node's `cost` is one // computation's price times `multiplier`, the times it is computed: // once if materialized, else once per query (or materialized node) // that reads it, so a shared NotMaterialized node is charged once per @@ -51,9 +56,9 @@ // does (`--max-displayed`). // // `--promql` and `--sql` may repeat and mix, in workload order; `--name` -// names the query before it (default `q`). `--epsilon`/ -// `--delta` apply to every query; without them the queries are exact. -// `--metric` (`absolute`, `absolute_stream_l1`, `absolute_stream_l2`, +// names the query before it (default `q`). `--max-error` (ε) and +// `--failure-probability` (δ) apply to every query; without them the +// queries are exact. `--accuracy-metric` (`absolute`, `absolute_stream_l1`, `absolute_stream_l2`, // `relative` or `rank`) sets the error metric of that target; without it, // a candidate is accepted in whatever metric its guarantee meets ε (and δ) // in, and `stage3_plan_selection.accepted` reports, per shown valid @@ -131,7 +136,7 @@ use serde_json::{json, Value}; const USAGE: &str = "usage: stage_pipeline (--example planner-layering-{1,2,3a,3b} | ((--promql | --sql) \ -[--name ]... [--table ...] [--epsilon --delta --metric ] [--sample-interval-ms ])) \ +[--name ]... [--table ...] [--max-error --failure-probability --accuracy-metric ] [--sample-interval-ms ])) \ [--cost-model usage-over-time|provisioned-capacity|cluster-capacity [--cluster-memory-bytes \ --cluster-disk-bytes [--cluster-cpu-cores ] --cluster-objective lowest-latency|least-memory|pareto-front]] \ [--deployment-capabilities ] [--max-combinations ] [--max-displayed ] --out "; @@ -214,8 +219,8 @@ fn run(args: Vec) -> Result<(), Failure> { // (language, text, name) in workload order. let mut queries: Vec<(QueryLanguage, String, Option)> = Vec::new(); let mut tables = Vec::new(); - let (mut epsilon, mut delta, mut sample_interval_ms) = (None, None, 15_000u64); - let mut metric: Option = None; + let (mut max_error, mut failure_probability, mut sample_interval_ms) = (None, None, 15_000u64); + let mut accuracy_metric: Option = None; let mut max_displayed = 64usize; let mut max_combinations = MAX_COMBINATIONS; let mut cost_model: Option = None; @@ -244,11 +249,11 @@ fn run(args: Vec) -> Result<(), Failure> { } } "--table" => tables.push(value()?), - "--epsilon" => epsilon = Some(number(value()?)?), - "--delta" => delta = Some(number(value()?)?), - "--metric" => { + "--max-error" => max_error = Some(number(value()?)?), + "--failure-probability" => failure_probability = Some(number(value()?)?), + "--accuracy-metric" => { let name = value()?; - metric = Some( + accuracy_metric = Some( serde_json::from_value(Value::String(name.clone())) .map_err(|_| format!("unknown metric {name}"))?, ) @@ -369,16 +374,18 @@ fn run(args: Vec) -> Result<(), Failure> { document_of(workload_queries(&workload), &roots, result, max_displayed)? } (None, false) => { - let accuracy = match (epsilon, delta) { + let accuracy = match (max_error, failure_probability) { (None, None) => AccuracyTarget::exact(), - (Some(epsilon), None) => AccuracyTarget::max_error(epsilon), - (Some(epsilon), Some(delta)) => { - AccuracyTarget::max_error(epsilon).failure_probability(delta) + (Some(max_error), None) => AccuracyTarget::max_error(max_error), + (Some(max_error), Some(failure_probability)) => { + AccuracyTarget::max_error(max_error).failure_probability(failure_probability) } - (None, Some(_)) => return Err("--delta needs --epsilon".into()), + (None, Some(_)) => return Err("--failure-probability needs --max-error".into()), }; - let accuracy = match metric { - Some(_) if epsilon.is_none() => return Err("--metric needs --epsilon".into()), + let accuracy = match accuracy_metric { + Some(_) if max_error.is_none() => { + return Err("--accuracy-metric needs --max-error".into()) + } Some(metric) => accuracy.accuracy_metric(metric), None => accuracy, }; @@ -646,7 +653,7 @@ fn usage_json(usage: &ResourceUsage) -> Value { fn rejections_json<'a>(rejected: impl Iterator) -> Vec { rejected - .map(|r| json!({ "id": r.id, "valid": r.valid, "reason": r.reason })) + .map(|r| json!({ "id": r.id, "cause": r.cause, "reason": r.reason })) .collect() } @@ -810,7 +817,7 @@ mod tests { fn failed_selection_lists_rejections_without_a_selection() { let rejected = vec![Rejection { id: "P1".into(), - valid: false, + cause: asap_aware_mapping::stage3_plan_selection::RejectionCause::AccuracyTargetMissed, reason: "q1: misses target".into(), }]; let document = @@ -820,7 +827,11 @@ mod tests { json!({ "costs": {}, "selected": null, - "rejected": [{ "id": "P1", "valid": false, "reason": "q1: misses target" }], + "rejected": [{ + "id": "P1", + "cause": "accuracy_target_missed", + "reason": "q1: misses target", + }], }) ); } diff --git a/crates/devtools/tests/stage_pipeline.rs b/crates/devtools/tests/stage_pipeline.rs index 553d5708..10321daf 100644 --- a/crates/devtools/tests/stage_pipeline.rs +++ b/crates/devtools/tests/stage_pipeline.rs @@ -133,10 +133,17 @@ fn example1_document_is_valid_and_committed_fixture_is_current() { let id = rejection["id"].as_str().unwrap(); assert!(accounted.insert(id), "{id} accounted for once"); assert!(!rejection["reason"].as_str().unwrap().is_empty()); - assert_eq!( - rejection["valid"].as_bool().unwrap(), - costs.contains_key(id) - ); + // A candidate that failed a check is not priced; one that lost + // under the cost model is. + let failed_a_check = [ + "failure_probability_resize_failed", + "accuracy_target_missed", + "raw_data_not_retained", + "latency_bound_exceeded", + "over_cluster_capacity", + ] + .contains(&rejection["cause"].as_str().unwrap()); + assert_eq!(failed_a_check, !costs.contains_key(id), "{rejection}"); } assert!(costs.contains_key(selected)); let all: HashSet<_> = physical.iter().map(|p| p["id"].as_str().unwrap()).collect(); @@ -172,9 +179,9 @@ fn uncomposable_candidate_is_recorded_with_its_error() { "--promql", // Top-K partitioned by every label of its input has no item. "topk by (job) (3, sum by (job) (rate(m[5m])))", - "--epsilon", + "--max-error", "0.01", - "--delta", + "--failure-probability", "0.001", "--out", ]) @@ -309,7 +316,7 @@ fn mixed_sql_and_promql_queries_are_planned_with_their_names() { "count_by_service", "--promql", "topk(5, rate(http_requests_total[5m]))", - "--epsilon", + "--max-error", "0.01", "--out", ]) @@ -414,7 +421,7 @@ fn default_candidate_limit_is_ten_thousand() { for query in &queries { args.extend(["--promql", query.as_str()]); } - args.extend(["--epsilon", "0.01", "--delta", "0.001"]); + args.extend(["--max-error", "0.01", "--failure-probability", "0.001"]); let (status, stderr) = failure(&args); assert_eq!(status, Some(3)); assert!( @@ -521,9 +528,9 @@ fn pass1_failure_still_writes_stage0_and_the_error() { let document = document_of(&[ "--promql", "topk(5, rate(m[5m]))", - "--epsilon", + "--max-error", "1.5", - "--delta", + "--failure-probability", "0.1", ]); validated_dag(&document["stage0_query_language_frontends"]["dag"], 1); @@ -617,7 +624,7 @@ fn a_target_without_a_metric_shows_the_query_default() { /// A quantile query's target may leave its metric unset: then KLL (rank) /// and DDSketch (relative) candidates both meet it and compete on cost, and /// the document reports, per valid candidate, the metric each was accepted -/// in and that the target's metric was not set. With `--metric rank` only +/// in and that the target's metric was not set. With `--accuracy-metric rank` only /// rank guarantees (and exact plans) are accepted, and the report says so. The workload's /// accuracy is the target's own JSON form. #[test] @@ -625,7 +632,7 @@ fn a_quantile_target_without_a_metric_accepts_rank_or_relative() { let query = [ "--promql", "quantile_over_time(0.99, m[1m])", - "--epsilon", + "--max-error", "0.01", ]; let accepted = |document: &Value| -> Vec { @@ -641,7 +648,7 @@ fn a_quantile_target_without_a_metric_accepts_rank_or_relative() { let unset = document_of(&query); assert_eq!( unset["workload"]["queries"][0]["requirements"]["accuracy"], - serde_json::json!({"error": 0.01, "accuracy_scope": "whole_result"}) + serde_json::json!({"max_error": 0.01, "accuracy_scope": "whole_result"}) ); // A quantile has no single default metric to show. assert!(unset["workload"]["queries"][0]["requirements"] @@ -663,7 +670,7 @@ fn a_quantile_target_without_a_metric_accepts_rank_or_relative() { let selected = unset["stage3_plan_selection"]["selected"].as_str().unwrap(); assert!(unset["stage3_plan_selection"]["accepted"][selected][0]["accepted_metric"].is_string()); - let rank = document_of(&[&query[..], &["--metric", "rank"]].concat()); + let rank = document_of(&[&query[..], &["--accuracy-metric", "rank"]].concat()); let summaries = accepted(&rank); assert!( summaries diff --git a/crates/frontend-promql/tests/promql_conformance.rs b/crates/frontend-promql/tests/promql_conformance.rs index 62d0c769..0a06627e 100644 --- a/crates/frontend-promql/tests/promql_conformance.rs +++ b/crates/frontend-promql/tests/promql_conformance.rs @@ -728,7 +728,7 @@ fn count_maps_to_count_and_inherits_accuracy() { assert!( has(&approx, |i| matches!( i, - AggIntent::Count { accuracy } if (accuracy.error - 0.01).abs() < 1e-9 + AggIntent::Count { accuracy } if (accuracy.max_error - 0.01).abs() < 1e-9 && accuracy.accuracy_metric.is_none() )), "Count must carry the approximate target, got {:?}", diff --git a/crates/frontend-promql/tests/promql_lowering.rs b/crates/frontend-promql/tests/promql_lowering.rs index 0b15e521..ddd5eddd 100644 --- a/crates/frontend-promql/tests/promql_lowering.rs +++ b/crates/frontend-promql/tests/promql_lowering.rs @@ -1110,7 +1110,7 @@ fn accuracy_target_flows_into_quantile_intent() { }; assert!(matches!( &measures[0], - AggIntent::Quantile { accuracy, .. } if (accuracy.error - 0.01).abs() < 1e-12 + AggIntent::Quantile { accuracy, .. } if (accuracy.max_error - 0.01).abs() < 1e-12 )); } diff --git a/crates/frontend-sql/tests/sql_lowering.rs b/crates/frontend-sql/tests/sql_lowering.rs index 0a752461..4193d2ee 100644 --- a/crates/frontend-sql/tests/sql_lowering.rs +++ b/crates/frontend-sql/tests/sql_lowering.rs @@ -1468,7 +1468,7 @@ async fn median_threads_the_accuracy_target() { matches!( measures.as_slice(), [AggIntent::Quantile { accuracy, .. }] - if (accuracy.error - 0.01).abs() < 1e-12 + if (accuracy.max_error - 0.01).abs() < 1e-12 ), "median must carry the workload's accuracy target, got {measures:?}" ); diff --git a/crates/integration-tests/tests/operator_design_examples.rs b/crates/integration-tests/tests/operator_design_examples.rs index aec64fbe..c59d57de 100644 --- a/crates/integration-tests/tests/operator_design_examples.rs +++ b/crates/integration-tests/tests/operator_design_examples.rs @@ -375,7 +375,7 @@ async fn batch_planning_replaces_summary_operators() { } // The batch exports as one physical DAG: a root per query and the one // SUM state, with the materialization the selected plan assigned it. - let workload_dag = output.execution_timed_dag().unwrap(); + let workload_dag = output.physical_asap_dag().unwrap(); assert_eq!(workload_dag.roots.len(), 2); assert_ne!(workload_dag.roots[0], workload_dag.roots[1]); let states: Vec<_> = workload_dag diff --git a/crates/integration-tests/tests/planner_layering_common/mod.rs b/crates/integration-tests/tests/planner_layering_common/mod.rs index e5985adb..dafee9bb 100644 --- a/crates/integration-tests/tests/planner_layering_common/mod.rs +++ b/crates/integration-tests/tests/planner_layering_common/mod.rs @@ -142,7 +142,7 @@ impl Run { self.selection .rejected .iter() - .filter(|r| !r.valid) + .filter(|r| r.cause.failed_a_check()) .map(|r| (r.id.as_str(), r.reason.as_str())) .collect() } diff --git a/crates/integration-tests/tests/planner_layering_example4.rs b/crates/integration-tests/tests/planner_layering_example4.rs index 2c443049..bec54824 100644 --- a/crates/integration-tests/tests/planner_layering_example4.rs +++ b/crates/integration-tests/tests/planner_layering_example4.rs @@ -16,7 +16,8 @@ mod planner_layering_common; use std::collections::{BTreeMap, BTreeSet}; use asap_aware_mapping::stage3_plan_selection::{ - ClusterLimits, ClusterObjective, CostModel, PlanSelectionSettings, ResourceUnitCost, + ClusterLimits, ClusterObjective, CostModel, PlanSelectionSettings, RejectionCause, + ResourceUnitCost, }; use asap_aware_mapping::DeploymentCapabilities; use asap_types::ir::flat::NodeId; @@ -499,7 +500,7 @@ fn stage3_plan_selection_b_pareto_front_over_memory_disk_and_latency() { .find(|r| r.id == ids.b2) .unwrap(); assert!( - b2.valid + b2.cause == RejectionCause::Dominated && b2 .reason .starts_with(&format!("dominated by {}", ids.whole_window_kll)), diff --git a/crates/integration-tests/tests/promql_to_post_asap.rs b/crates/integration-tests/tests/promql_to_post_asap.rs index a12a1bbe..baf16d52 100644 --- a/crates/integration-tests/tests/promql_to_post_asap.rs +++ b/crates/integration-tests/tests/promql_to_post_asap.rs @@ -788,7 +788,7 @@ fn ddsketch_ratio_bound_holds_for_signed_pinned_sketch_evaluations() { assert_eq!(alphas.len(), 2); assert_eq!(alphas[0], alphas[1]); let alpha = alphas[0]; - let bound = target.error; + let bound = target.max_error; for values in [ vec![sign; 30], (1..=100).map(|i| sign * i as f64).collect(), diff --git a/crates/planner/tests/summary_sharing.rs b/crates/planner/tests/summary_sharing.rs index 4585ebb8..c901eabd 100644 --- a/crates/planner/tests/summary_sharing.rs +++ b/crates/planner/tests/summary_sharing.rs @@ -11,6 +11,7 @@ use asap_aware_mapping::pass::{ run_stage1_to_stage3, PlanOutput, PlanningModels, QueryPlan, Stage1ToStage3Run, }; use asap_aware_mapping::realization::{default_size_params, DEFAULT_DELTA}; +use asap_aware_mapping::stage3_plan_selection::RejectionCause; use asap_frontend_promql::{lower_promql_query_workload, promql_metric_names}; use asap_frontend_sql::SqlCatalog; use asap_planner::{e2e_plan, FrontendInput, UserInput}; @@ -408,7 +409,7 @@ async fn scalar_queries_read_planned_sub_dags() { // The export keys each scalar query by its entry and points the scalar // read at an exported node. - let dag = output.execution_timed_dag().unwrap(); + let dag = output.physical_asap_dag().unwrap(); let keys: Vec<_> = dag.scalar_roots.iter().map(|(i, _)| *i).collect(); assert_eq!(keys, vec![0, 1]); assert_eq!(dag.roots.len(), 1); @@ -444,7 +445,11 @@ fn combined_scalar_references_keep_summary_candidates() { .iter() .find(|r| r.id == both.id) .expect("not selected"); - assert!(!rejection.valid, "{rejection:?}"); + assert_eq!( + rejection.cause, + RejectionCause::AccuracyTargetMissed, + "{rejection:?}" + ); assert!( rejection.reason.contains("no guarantee"), "{}", @@ -533,7 +538,7 @@ async fn exact_state_roots_are_finalized() { matches!(root.asap(), Some(ASAPOp::FinalizeExactAccumulator { .. })), "{query}: {root:#?}" ); - let dag = output.execution_timed_dag().unwrap(); + let dag = output.physical_asap_dag().unwrap(); let root = dag .nodes .iter() diff --git a/crates/types/src/pre_asap/agg_intent.rs b/crates/types/src/pre_asap/agg_intent.rs index d5e0ee0c..ca74e887 100644 --- a/crates/types/src/pre_asap/agg_intent.rs +++ b/crates/types/src/pre_asap/agg_intent.rs @@ -710,7 +710,7 @@ pub fn agg_accuracy(op: &AggIntent) -> f64 { } fn accuracy_target_to_f64(t: &AccuracyTarget) -> f64 { - t.error + t.max_error } /// Default `Cardinality` intent over the sample value — HLL standard error at @@ -914,9 +914,9 @@ mod tests { #[test] fn cardinality_rejects_legacy_col_payloads() { for payload in [ - r#"{"kind":"cardinality","col":2,"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, - r#"{"kind":"cardinality","col":null,"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, - r#"{"kind":"cardinality","col":2,"cols":[1],"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":2,"accuracy":{"accuracy_metric":"absolute","max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":null,"accuracy":{"accuracy_metric":"absolute","max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":2,"cols":[1],"accuracy":{"accuracy_metric":"absolute","max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, ] { assert!( serde_json::from_str::(payload).is_err(), @@ -929,7 +929,7 @@ mod tests { /// with no `col` key — still deserializes, as the sample-value convention `None`. #[test] fn agg_intent_serde_reads_pre_115_payloads() { - let legacy = r#"{"kind":"quantile","q":0.99,"accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; + let legacy = r#"{"kind":"quantile","q":0.99,"accuracy":{"max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::Quantile { @@ -939,7 +939,7 @@ mod tests { } ); - let legacy = r#"{"kind":"top_k","k":5,"accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; + let legacy = r#"{"kind":"top_k","k":5,"accuracy":{"max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::TopK { @@ -948,7 +948,7 @@ mod tests { } ); - let legacy = r#"{"kind":"cardinality","accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; + let legacy = r#"{"kind":"cardinality","accuracy":{"max_error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::Cardinality { diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index be6c80aa..1e56792e 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -119,12 +119,14 @@ pub enum AccuracyScope { /// ([`crate::post_asap::AccuracyGuarantee`]): /// `Pr[err_metric(estimate, truth) > error] ≤ failure_probability` over the /// values `accuracy_scope` names. An [`AccuracyTarget`] has the same fields, with -/// concrete numbers and an optional metric. +/// concrete numbers, an optional metric and `max_error` for `error`: a target +/// states the largest error a query accepts, a guarantee the error bound +/// (an expression over the plan's parameters) its plan achieves. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AccuracySpec { pub accuracy_metric: AccuracyMetric, - /// ε, in `accuracy_metric`'s units. + /// ε: the error bound, in `accuracy_metric`'s units. pub error: E, /// δ. pub failure_probability: P, @@ -132,7 +134,7 @@ pub struct AccuracySpec { } /// An accuracy requirement, as a workload, a caller or a frontend states -/// it: `Pr[err_metric(estimate, truth) > error] ≤ failure_probability` over +/// it: `Pr[err_metric(estimate, truth) > max_error] ≤ failure_probability` over /// the values `accuracy_scope` names. /// /// `accuracy_metric: None` means the user did not set one: plan selection @@ -140,24 +142,24 @@ pub struct AccuracySpec { /// target's, and reports the metric it accepted. A set metric accepts only /// guarantees in that metric (or convertible to it with evidence). /// `failure_probability: None` imposes no δ requirement (sizing then uses -/// the planner's default δ). Exact is `error = 0`, +/// the planner's default δ). Exact is `max_error = 0`, /// `failure_probability = Some(0)`, whatever the metric. /// /// Built with [`Self::exact`] or [`Self::max_error`] plus the optional /// builders [`Self::failure_probability`], [`Self::accuracy_metric`] and /// [`Self::accuracy_scope`]. /// -/// JSON: `{"accuracy_metric": "rank", "error": 0.01, "failure_probability": -/// 0.001, "accuracy_scope": "whole_result"}`; every field but `error` may be +/// JSON: `{"accuracy_metric": "rank", "max_error": 0.01, "failure_probability": +/// 0.001, "accuracy_scope": "whole_result"}`; every field but `max_error` may be /// omitted (`accuracy_scope` defaults to `whole_result`). Exact is -/// `{"error": 0.0, "failure_probability": 0.0}`. +/// `{"max_error": 0.0, "failure_probability": 0.0}`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AccuracyTarget { #[serde(default, skip_serializing_if = "Option::is_none")] pub accuracy_metric: Option, - /// ε, in the metric's units. - pub error: f64, + /// ε: the largest error the query accepts, in the metric's units. + pub max_error: f64, /// δ. #[serde(default, skip_serializing_if = "Option::is_none")] pub failure_probability: Option, @@ -174,18 +176,18 @@ impl AccuracyTarget { pub const fn exact() -> Self { Self { accuracy_metric: None, - error: 0.0, + max_error: 0.0, failure_probability: Some(0.0), accuracy_scope: AccuracyScope::WholeResult, } } - /// Error at most `error` over the whole result, with no δ requirement - /// and no metric set. - pub const fn max_error(error: f64) -> Self { + /// Error at most `max_error` over the whole result, with no δ + /// requirement and no metric set. + pub const fn max_error(max_error: f64) -> Self { Self { accuracy_metric: None, - error, + max_error, failure_probability: None, accuracy_scope: AccuracyScope::WholeResult, } @@ -217,7 +219,7 @@ impl AccuracyTarget { /// Whether this target permits no approximation. pub fn is_exact(&self) -> bool { - self.error == 0.0 && self.failure_probability == Some(0.0) + self.max_error == 0.0 && self.failure_probability == Some(0.0) } } @@ -471,7 +473,7 @@ mod tests { } /// A target reads and writes as a plain struct: every field but - /// `error` may be omitted (no metric, no δ, the whole result), an unset + /// `max_error` may be omitted (no metric, no δ, the whole result), an unset /// metric or δ writes nothing, and the old named forms (`"Exact"`, /// `{"Epsilon": ε}`, `{"EpsilonDelta": …}`) and unknown fields are /// rejected. @@ -480,35 +482,37 @@ mod tests { use serde_json::json; let read = |value: serde_json::Value| serde_json::from_value::(value); assert_eq!( - read(json!({"error": 0.01})).unwrap(), + read(json!({"max_error": 0.01})).unwrap(), AccuracyTarget::max_error(0.01) ); assert_eq!( - read(json!({"error": 0.0, "failure_probability": 0.0})).unwrap(), + read(json!({"max_error": 0.0, "failure_probability": 0.0})).unwrap(), AccuracyTarget::exact() ); let rank = AccuracyTarget::max_error(0.01) .failure_probability(0.001) .accuracy_metric(AccuracyMetric::Rank); assert_eq!( - read(json!({"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.001})) - .unwrap(), + read( + json!({"accuracy_metric": "rank", "max_error": 0.01, "failure_probability": 0.001}) + ) + .unwrap(), rank ); assert_eq!( serde_json::to_value(&rank).unwrap(), - json!({"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.001, + json!({"accuracy_metric": "rank", "max_error": 0.01, "failure_probability": 0.001, "accuracy_scope": "whole_result"}) ); assert_eq!( serde_json::to_value(AccuracyTarget::max_error(0.01)).unwrap(), - json!({"error": 0.01, "accuracy_scope": "whole_result"}) + json!({"max_error": 0.01, "accuracy_scope": "whole_result"}) ); for old in [ json!("Exact"), json!({"Epsilon": 0.01}), json!({"EpsilonDelta": {"epsilon": 0.01, "delta": 0.001}}), - json!({"error": 0.1, "typo": true}), + json!({"max_error": 0.1, "typo": true}), ] { assert!(read(old.clone()).is_err(), "{old}"); } diff --git a/crates/types/src/workload.rs b/crates/types/src/workload.rs index 1ca6c8bb..40a107b3 100644 --- a/crates/types/src/workload.rs +++ b/crates/types/src/workload.rs @@ -59,7 +59,7 @@ pub enum QueryLanguage { /// Independent accuracy and response-latency constraints attached to one /// query in the workload. /// -/// JSON: `{"accuracy": {"error": 0.01, "accuracy_metric": "rank"}, "max_latency_ms": +/// JSON: `{"accuracy": {"max_error": 0.01, "accuracy_metric": "rank"}, "max_latency_ms": /// 200.0}`. An omitted `accuracy` is [`AccuracyTarget::exact`] and an /// omitted `max_latency_ms` imposes no bound. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -672,18 +672,18 @@ mod tests { let empty = serde_json::to_value(parse("{}").unwrap()).unwrap(); assert_eq!( empty, - serde_json::json!({"accuracy": {"error": 0.0, "failure_probability": 0.0, + serde_json::json!({"accuracy": {"max_error": 0.0, "failure_probability": 0.0, "accuracy_scope": "whole_result"}, "max_latency_ms": null}) ); let bounded = serde_json::to_value( - parse(r#"{"accuracy": {"error": 0.01}, "max_latency_ms": 200.0}"#).unwrap(), + parse(r#"{"accuracy": {"max_error": 0.01}, "max_latency_ms": 200.0}"#).unwrap(), ) .unwrap(); - assert_eq!(bounded["accuracy"]["error"], 0.01); + assert_eq!(bounded["accuracy"]["max_error"], 0.01); assert_eq!(bounded["max_latency_ms"], 200.0); for old in [ r#"{"accuracy": "implicit_exact", "response_latency": "unspecified"}"#, - r#"{"accuracy": {"explicit": {"error": 0.01}}}"#, + r#"{"accuracy": {"explicit": {"max_error": 0.01}}}"#, r#"{"response_latency": {"explicit_max_ms": 100.0}}"#, r#"{"max_latency_ms": 100.0, "unknown": 1}"#, ] { @@ -993,7 +993,7 @@ mod tests { })) .is_err()); assert!(serde_json::from_value::(serde_json::json!({ - "error": 0.1, "failure_probability": 0.01, "typo": true + "max_error": 0.1, "failure_probability": 0.01, "typo": true })) .is_err()); } diff --git a/docs/design_docs/architecture/input-output-workflow.md b/docs/design_docs/architecture/input-output-workflow.md index 13a76adb..98b3bdc7 100644 --- a/docs/design_docs/architecture/input-output-workflow.md +++ b/docs/design_docs/architecture/input-output-workflow.md @@ -28,7 +28,7 @@ and [frontend dependencies](#frontend-specific-dependencies). | Output | Fields or contents | Meaning | |---|---|---| -| `PlanOutput` | `plans`: one `QueryPlan { entry_index, root }` per query; `selection`: Stage 3's `Selection` (`selected`, `plan`, `costs`, `accepted`, `rejected`, `front`), set by the default pass `Stage1ToStage3Pass`; standalone scalar queries in `scalar_roots` and `scalar_plans` | The selected plan; `execution_timed_dag()` exports it as one `PhysicalASAPDAG` | +| `PlanOutput` | `plans`: one `QueryPlan { entry_index, root }` per query; `selection`: Stage 3's `Selection` (`selected`, `plan`, `costs`, `accepted`, `rejected`, `front`), set by the default pass `Stage1ToStage3Pass`; standalone scalar queries in `scalar_roots` and `scalar_plans` | The selected plan; `physical_asap_dag()` exports it as one `PhysicalASAPDAG` | | `Stage1ToStage3Run` (from `run_stage1_to_stage3`) | Every stage's candidates: Pass 1's per-computation candidates, the logical candidates, the physical candidates, Stage 2's pruned candidates, and the selection | For callers that inspect or display the alternatives | The selected plan is a planning artifact. ASAPPlanner does **not** deploy diff --git a/docs/design_docs/architecture/updated_interface_with_pluggable_optimization.md b/docs/design_docs/architecture/updated_interface_with_pluggable_optimization.md index a77a602d..946aed86 100644 --- a/docs/design_docs/architecture/updated_interface_with_pluggable_optimization.md +++ b/docs/design_docs/architecture/updated_interface_with_pluggable_optimization.md @@ -100,7 +100,7 @@ pub struct QueryPlan { ``` `Stage1ToStage3Pass` returns the selected physical candidate's roots, with every -sub-DAG `NotMaterialized`; `PlanOutput::execution_timed_dag()` exports them +sub-DAG `NotMaterialized`; `PlanOutput::physical_asap_dag()` exports them with that same assignment. Physical ASAP-aware optimization decides per sub-DAG whether to materialize, and whether at ingestion or query time, from #604 on. diff --git a/docs/design_docs/physical-planning-and-deployment.md b/docs/design_docs/physical-planning-and-deployment.md index dedec597..22d06f1f 100644 --- a/docs/design_docs/physical-planning-and-deployment.md +++ b/docs/design_docs/physical-planning-and-deployment.md @@ -53,7 +53,7 @@ decided by a `MaterializationAssignment`, as the layer contract below states. every node's `ExecutionTiming` and `Materialization`: a `MatAtIngestion` state and all of its inputs run at ingestion time; readouts, other consumers, and the other states run at query time. The default assignment - is `NotMaterialized` everywhere, which `PlanOutput::execution_timed_dag` + is `NotMaterialized` everywhere, which `PlanOutput::physical_asap_dag` applies. 3. **Physical compile** (Planner) reads timing: ingestion-time nodes form the precompute DAG and the rest form the query DAG, joined by typed outputs. It @@ -402,7 +402,7 @@ node does not depend on the frontier, so each query DAG is lowered once and different materialization assignments are different cuts of that lowering. `compile(dag, inputs, roots)` yields the complete `CompiledPhysicalDAG`. `frontier_from_timing(&timed_dag)` reads an assignment's timed DAG (from -`execution_timed_dag`) and returns its frontier: ingestion-time nodes read by +`physical_asap_dag`) and returns its frontier: ingestion-time nodes read by query-time nodes, or an ingestion-time root; a query-time node feeding an ingestion-time node is rejected. `cut_candidate(&compiled, &frontier)` then partitions the lowered operators: the frontier's ancestors form the precompute diff --git a/docs/develop_docs/asap-aware-mapping-contracts.md b/docs/develop_docs/asap-aware-mapping-contracts.md index 22669b7c..1c06ff62 100644 --- a/docs/develop_docs/asap-aware-mapping-contracts.md +++ b/docs/develop_docs/asap-aware-mapping-contracts.md @@ -93,9 +93,12 @@ details are in the rustdoc (`cargo doc -p asap-aware-mapping -p asap-types `EntryCount` and `InvalidDeploymentCapabilities` are bad input and fail the whole selection. - Each candidate is then either invalid or valid, recorded as a `Rejection` - with its reason (`valid: false` failed a check: accuracy, latency, raw data - not retained, or a cluster limit under cost model 3; `valid: true` lost - under the cost model). A candidate that cannot be resized for its failure + with its `cause` (`RejectionCause`) and reason: a failed check + (`FailureProbabilityResizeFailed`, `AccuracyTargetMissed`, + `RawDataNotRetained`, `LatencyBoundExceeded`, or `OverClusterCapacity` + under cost model 3) or a loss under the cost model (`Costlier`, `Slower`, + `MoreMemory`, `NotLowestLatencyOnParetoFront`, `Dominated`, + `TiedLostOnEnumerationOrder`). A candidate that cannot be resized for its failure probability is rejected alone. - Usage (`ResourceUsage`) is measured without prices; the `CostModel` in `PlanSelectionSettings` turns it into a choice. Ties keep enumeration order diff --git a/docs/develop_docs/extend-asap-aware-mapping.md b/docs/develop_docs/extend-asap-aware-mapping.md index 6de779a8..b27f4a1c 100644 --- a/docs/develop_docs/extend-asap-aware-mapping.md +++ b/docs/develop_docs/extend-asap-aware-mapping.md @@ -83,7 +83,8 @@ tests in `planner_layering_example4.rs`. ## 4. Add a Stage 3 check or a deployment input - A new validity check goes in `select_plan` and rejects a candidate with a - `Rejection { valid: false, reason }`; it never fails the whole selection + `Rejection { cause, reason }` whose `cause` is a new failed-check + `RejectionCause` variant (listed in `RejectionCause::failed_a_check`); it never fails the whole selection unless the input itself is bad (a new `SelectionError` variant, documented as bad input). - A new pricing input goes in `PlanSelectionSettings` (validated in its diff --git a/docs/develop_docs/library-api.md b/docs/develop_docs/library-api.md index 9a8cd903..bc97bd7a 100644 --- a/docs/develop_docs/library-api.md +++ b/docs/develop_docs/library-api.md @@ -16,7 +16,7 @@ plan, and a serializable DAG is not evidence of runtime readiness. | Pre-ASAP IR | Frontend `lower_*` | [Lower a query](#lower-a-query-into-pre-asap-ir) | | Selected plan for a workload (Stages 0–3) | `asap_planner::e2e_plan` | [Plan a workload](#plan-a-workload-through-the-stages) | | Every stage's candidates for already lowered roots | `asap_aware_mapping::run_stage1_to_stage3` | [Plan a workload](#plan-a-workload-through-the-stages) | -| Physical DAG for an executor | `PlanOutput::execution_timed_dag`, or timing and export functions | [Export](#export) | +| Physical DAG for an executor | `PlanOutput::physical_asap_dag`, or timing and export functions | [Export](#export) | Each recipe ends at a different artifact. Use only the stages needed for that artifact, while preserving the checks required by its intended consumer. @@ -89,7 +89,7 @@ own accuracy requirement, an `AccuracyTarget` with the fields `error` (ε), | `.accuracy_scope(s)` | Which output values one statement covers (`AccuracyScope`) | `.accuracy_scope(AccuracyScope::WholeResult)` | In JSON the target is a plain struct, for example -`{"error": 0.01, "failure_probability": 0.001, "accuracy_metric": "rank"}`. +`{"max_error": 0.01, "failure_probability": 0.001, "accuracy_metric": "rank"}`. Epsilon does not mean the same error quantity for every statistic. Inspect the candidate's guarantee and its error metric; a target is a requirement, not proof @@ -182,7 +182,7 @@ asap_aware_mapping::run_stage1_to_stage3( | `CostModel::ClusterCapacity(ClusterLimits)` | Cost model 3: memory, disk and optional CPU cores are hard limits; `ClusterObjective::{LowestLatency, LeastMemory, ParetoFront}` chooses among the candidates that fit | | `PlanOutput.plans` | One `QueryPlan { entry_index, root }` per query, in `QueryWorkload::entries()` order | | `PlanOutput.selection` | `Some` with Stage 3's `Selection` under the default pass: `selected`, `costs` (each candidate's `CandidateCost` with its `ResourceUsage` and `cost_per_second`, `None` under cost model 3), `accepted`, `rejected` (with reasons) and `front` | -| `PlanOutput::execution_timed_dag()` | The planned queries as one `PhysicalASAPDAG`, timed with the default `MaterializationAssignment` (see [Export](#export)) | +| `PlanOutput::physical_asap_dag()` | The planned queries as one `PhysicalASAPDAG`, timed with the default `MaterializationAssignment` (see [Export](#export)) | Each candidate's `ResourceUsage` holds its usage without prices: `cpu_ops_per_second`, `scanned_bytes_per_second`, `average_memory_bytes`, @@ -246,7 +246,7 @@ async fn main() -> Result<(), Box> { for rejection in &selection.rejected { println!("{}: {}", rejection.id, rejection.reason); } - let dag = output.execution_timed_dag()?; + let dag = output.physical_asap_dag()?; println!("{} physical nodes", dag.nodes.len()); Ok(()) } @@ -319,7 +319,7 @@ optional also does not guarantee every planning operation can succeed without it | Function/type | Purpose | | --- | --- | -| `asap_types::ir::apply_materialization_timings(&root, &assignment, &mut TimingMemo::new())` | Write execution timing into every node from a `MaterializationAssignment` (default: all query time) and validate the data-state edges; `PlanOutput::execution_timed_dag()` applies the default to a planned workload | +| `asap_types::ir::apply_materialization_timings(&root, &assignment, &mut TimingMemo::new())` | Write execution timing into every node from a `MaterializationAssignment` (default: all query time) and validate the data-state edges; `PlanOutput::physical_asap_dag()` applies the default to a planned workload | | `asap_types::ir::physical_export::compile_physical_asap_dag(&timed_root)` | Export a timed DAG as a `PhysicalASAPDAG` (`PHYSICAL_ASAP_DAG_WIRE_VERSION`); rejects an untimed node; `compile_physical_asap_workload(&roots)` exports a batch with one root per query | | `PhysicalASAPDAGDocument::new(dag)` and `.validate()` | Versioned semantic envelope and explicit validation; constructing it alone does not validate | diff --git a/docs/user_guide_docs/run-a-query.md b/docs/user_guide_docs/run-a-query.md index 324aa6c2..fe895016 100644 --- a/docs/user_guide_docs/run-a-query.md +++ b/docs/user_guide_docs/run-a-query.md @@ -20,7 +20,7 @@ tool on first use. | `analyze_corpora --corpora --sample-interval-ms 1000 --out-dir ` | Repository PromQL corpora, output directory | Writes successful/error IR dumps and summary reports | | `analyze_corpora --sql-corpora --out-dir ` | Repository SQL corpora, output directory | Writes SQL corpus reports | | `variant_coverage --sample-interval-ms 1000` | Repository corpora | Reports Pre-ASAP IR variant coverage | -| `sketch_coverage --sample-interval-ms 1000 --epsilon 0.01` | Repository corpora; epsilon defaults to `0.01` | Reports which summary candidates Pass 1 offers for each successfully lowered query | +| `sketch_coverage --sample-interval-ms 1000 --max-error 0.01` | Repository corpora; `--max-error` defaults to `0.01` | Reports which summary candidates Pass 1 offers for each successfully lowered query | ## Plan a workload @@ -31,7 +31,7 @@ ASAP-aware optimization (Stage 1), physical ASAP-aware optimization ```sh cargo run -p asap-devtools --bin stage_pipeline -- \ --promql "topk by (job) (10, sum_over_time(http_requests_total[1m]))" \ - --epsilon 0.01 --delta 0.001 --out plan.json + --max-error 0.01 --failure-probability 0.001 --out plan.json ``` The document's `stage3_plan_selection.selected` names the selected plan, and @@ -40,7 +40,7 @@ For this query, `selected` is `P1`, the exact plan. The Count-Min top-k plan (`P2`) is rejected because the command declares no metric type, so the samples are not proven non-negative, and the CountSketch top-k plan (`P3`) because its output has no guarantee; `stage3_plan_selection.rejected` gives -each reason. +each `cause` (here `accuracy_target_missed`) and reason. Open the document in the [Stage Viewer](stage-viewer.md) to see every stage, or run a built-in example of the design document, such as `--example planner-layering-1`. The Stage Viewer guide lists every option. @@ -115,10 +115,10 @@ cargo run -p asap-devtools --bin variant_coverage -- --sample-interval-ms 1000 Parse the same query corpora, lower them with an approximate `AccuracyTarget`, run Pass 1 of logical ASAP-aware optimization over each query, and report which summary candidates Pass 1 offers, plus the fraction of each corpus's successfully lowered queries with at least one: ```sh -cargo run -p asap-devtools --bin sketch_coverage -- --sample-interval-ms 1000 --epsilon 0.01 +cargo run -p asap-devtools --bin sketch_coverage -- --sample-interval-ms 1000 --max-error 0.01 ``` -`--epsilon` is optional (defaults to `0.01`). Without an approximate target, Pass 1 offers no summary candidate. See the binary's own doc comment for exactly how coverage is counted. +`--max-error` is optional (defaults to `0.01`). Without an approximate target, Pass 1 offers no summary candidate. See the binary's own doc comment for exactly how coverage is counted. ## Additional examples diff --git a/docs/user_guide_docs/stage-viewer.md b/docs/user_guide_docs/stage-viewer.md index 9cb0a4ee..78a32a91 100644 --- a/docs/user_guide_docs/stage-viewer.md +++ b/docs/user_guide_docs/stage-viewer.md @@ -202,7 +202,7 @@ another cost model, use the command below. ```sh cargo run -p asap-devtools --bin stage_pipeline -- \ --promql "topk by (job) (10, sum_over_time(http_requests_total[1m]))" \ - --epsilon 0.01 --delta 0.001 --out plan.json + --max-error 0.01 --failure-probability 0.001 --out plan.json ``` | Option | Meaning | @@ -210,8 +210,8 @@ cargo run -p asap-devtools --bin stage_pipeline -- \ | `--promql `, `--sql ` | A query; repeat and mix for several, in workload order | | `--name ` | Names the query before it (default `q1`, `q2`, …) | | `--table ''` | A table the SQL queries read, for example `{"name": "metrics", "columns": [{"name": "ts", "type": "timestamp"}, {"name": "service", "type": "utf8"}], "time_index": 0}`; repeat for several | -| `--epsilon`, `--delta` | Accuracy for every query; without them the queries are exact | -| `--metric ` | The error metric of that accuracy: `absolute`, `absolute_stream_l1`, `absolute_stream_l2`, `relative` or `rank`. Without it, a plan is accepted in whatever metric meets ε (and δ). | +| `--max-error`, `--failure-probability` | Accuracy (ε, δ) for every query; without them the queries are exact | +| `--accuracy-metric ` | The error metric of that accuracy: `absolute`, `absolute_stream_l1`, `absolute_stream_l2`, `relative` or `rank`. Without it, a plan is accepted in whatever metric meets ε (and δ). | | `--sample-interval-ms ` | Sample interval of every metric the PromQL queries read (default 15000) | | `--example ` | A built-in example instead of queries: `planner-layering-1`, `-2`, `-3a` or `-3b` | | `--cost-model ` | How plan selection compares valid plans: `usage-over-time` (cost model 1, the default: CPU, scanned bytes and memory over time, priced per second), `provisioned-capacity` (cost model 2: peak memory, peak CPU cores and disk, priced) or `cluster-capacity` (cost model 3: no prices) | diff --git a/tools/dag-viewer/README.md b/tools/dag-viewer/README.md index 4709fd49..f69da9f4 100644 --- a/tools/dag-viewer/README.md +++ b/tools/dag-viewer/README.md @@ -63,7 +63,7 @@ time column). The server runs `stage_pipeline` with `--sql`, `--promql`, { "format": "asap-stage-pipeline/v1", "workload": { "queries": [{ "id": "q1", "language": "promql", "text": "...", - "requirements": { "accuracy": { "error": 0.0, "failure_probability": 0.0, "accuracy_scope": "whole_result" }, + "requirements": { "accuracy": { "max_error": 0.0, "failure_probability": 0.0, "accuracy_scope": "whole_result" }, "repeat_interval_ms": 10000 } }] }, "deployment_capabilities": { "supported_summaries": null, "raw_data_retained": true, "query_time_results_retained": false }, @@ -91,7 +91,7 @@ time column). The server runs `stage_pipeline` with `--sql`, `--promql`, "accepted": { "P1": [{ "target_metric": null, "accepted_metric": "rank", "sketches": ["Kll"], "summary": "metric not set; accepted rank (Kll)" }] }, "selected": "P1", - "rejected": [{ "id": "P2", "valid": true, "reason": "..." }], + "rejected": [{ "id": "P2", "cause": "costlier", "reason": "..." }], "front": [] } } @@ -126,6 +126,12 @@ time column). The server runs `stage_pipeline` with `--sql`, `--promql`, - A node's `per_node` entry holds `cost_per_second`, `multiplier`, `detail` and `price_source` (`measured` when `--measured-operation-costs` priced it, else `analytical`). +- Each `stage3_plan_selection.rejected` entry has a `cause`: a failed check + (`failure_probability_resize_failed`, `accuracy_target_missed`, + `raw_data_not_retained`, `latency_bound_exceeded`, `over_cluster_capacity`) + or a loss under the cost model (`costlier`, `slower`, `more_memory`, + `not_lowest_latency_on_pareto_front`, `dominated`, + `tied_lost_on_enumeration_order`). - When Pass 1 fails, Stage 1 has `error` and no candidates. When selection fails, `selected` is null, with the rejections or with `error`. diff --git a/tools/dag-viewer/editor.js b/tools/dag-viewer/editor.js index d32985f6..6acc84ca 100644 --- a/tools/dag-viewer/editor.js +++ b/tools/dag-viewer/editor.js @@ -115,8 +115,8 @@ schemas: Array.from(schemaRows.querySelectorAll('.schemaRow')) .filter((row) => row.querySelector('.schemaInclude').checked) .map(parseSchemaRow), - epsilon: Number($('plannerEpsilon').value), - metric: $('plannerMetric').value, + max_error: Number($('plannerMaxError').value), + accuracy_metric: $('plannerAccuracyMetric').value, }), }); const result = await response.json(); diff --git a/tools/dag-viewer/examples/planner-layering-example1.json b/tools/dag-viewer/examples/planner-layering-example1.json index fc3f288e..03488a2a 100644 --- a/tools/dag-viewer/examples/planner-layering-example1.json +++ b/tools/dag-viewer/examples/planner-layering-example1.json @@ -550,8 +550,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -1472,8 +1472,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -2271,8 +2271,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -2991,8 +2991,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -3898,8 +3898,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -4682,8 +4682,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -5589,8 +5589,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -6373,8 +6373,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -7265,8 +7265,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -8034,8 +8034,8 @@ { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "k": 10, "kind": "top_k" @@ -105738,284 +105738,284 @@ "front": [], "rejected": [ { + "cause": "accuracy_target_missed", "id": "P10", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P11", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P12", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P13", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P13-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P14", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P14-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P15", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P15-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P16", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P16-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P17", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P17-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P17-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P17-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P18", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P18-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P18-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P18-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P19", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P20", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P21", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P22", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P22-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P23", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P23-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P24", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P24-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P25", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P25-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P26", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P26-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P26-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P26-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P27", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P27-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P27-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "accuracy_target_missed", "id": "P27-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", - "valid": false + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, max_error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }" }, { + "cause": "tied_lost_on_enumeration_order", "id": "P2", - "reason": "tied with the selected candidate P1 at 7.940 cost_per_second; lost on enumeration order", - "valid": true + "reason": "tied with the selected candidate P1 at 7.940 cost_per_second; lost on enumeration order" }, { + "cause": "tied_lost_on_enumeration_order", "id": "P3", - "reason": "tied with the selected candidate P1 at 7.940 cost_per_second; lost on enumeration order", - "valid": true + "reason": "tied with the selected candidate P1 at 7.940 cost_per_second; lost on enumeration order" }, { + "cause": "costlier", "id": "P4", - "reason": "costlier: 8.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 8.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P4-m1", - "reason": "costlier: 12.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 12.940 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P5", - "reason": "costlier: 8.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 8.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P5-m1", - "reason": "costlier: 12.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 12.940 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P6", - "reason": "costlier: 8.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 8.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P6-m1", - "reason": "costlier: 12.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 12.940 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P7", - "reason": "costlier: 8.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 8.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P7-m1", - "reason": "costlier: 12.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 12.940 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P8", - "reason": "costlier: 9.140 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 9.140 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P8-m1", - "reason": "costlier: 13.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 13.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P8-m2", - "reason": "costlier: 13.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 13.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P8-m3", - "reason": "costlier: 17.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 17.940 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P9", - "reason": "costlier: 9.140 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 9.140 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P9-m1", - "reason": "costlier: 13.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 13.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P9-m2", - "reason": "costlier: 13.540 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 13.540 vs 7.940 cost_per_second" }, { + "cause": "costlier", "id": "P9-m3", - "reason": "costlier: 17.940 vs 7.940 cost_per_second", - "valid": true + "reason": "costlier: 17.940 vs 7.940 cost_per_second" } ], "selected": "P1" @@ -106028,8 +106028,8 @@ "requirements": { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.0, - "failure_probability": 0.0 + "failure_probability": 0.0, + "max_error": 0.0 }, "repeat_interval_ms": 10000 }, @@ -106041,8 +106041,8 @@ "requirements": { "accuracy": { "accuracy_scope": "whole_result", - "error": 0.01, - "failure_probability": 0.001 + "failure_probability": 0.001, + "max_error": 0.01 }, "default_metric": "absolute_stream_l1", "max_latency_ms": 100.0, diff --git a/tools/dag-viewer/index.html b/tools/dag-viewer/index.html index 1ceb4656..9d7cfdbe 100644 --- a/tools/dag-viewer/index.html +++ b/tools/dag-viewer/index.html @@ -151,8 +151,8 @@

ASAPPlanner Stage Viewer

Queries

- - +