From b3dfc88149a7ea8634dfb80aa6955e0b85a59b3a Mon Sep 17 00:00:00 2001 From: zz_y Date: Tue, 6 Oct 2026 19:12:30 +0000 Subject: [PATCH] planner: accept optional measured_at on atomic-cost entries sketch-bench#155 (for sketch-bench#147) adds a measured_at block to every atomic-cost row. AtomicCostEntry denies unknown fields, so a regenerated table would fail to load. Carry it as an optional opaque Value. Co-Authored-By: Claude Opus 5.5 --- asap-planner-rs/src/optimizer/atomic_costs.rs | 27 +++++++++++++++++++ asap-planner-rs/src/optimizer/greedy.rs | 2 ++ 2 files changed, 29 insertions(+) diff --git a/asap-planner-rs/src/optimizer/atomic_costs.rs b/asap-planner-rs/src/optimizer/atomic_costs.rs index ceb3c887..b9d03cfa 100644 --- a/asap-planner-rs/src/optimizer/atomic_costs.rs +++ b/asap-planner-rs/src/optimizer/atomic_costs.rs @@ -83,6 +83,12 @@ pub struct AtomicCostEntry { pub merge_cpu_secs: f64, pub query_cpu_secs: f64, pub query_accuracy: BTreeMap, + /// The conditions sketch-bench measured the row under (items, keys and + /// value range per instance, merge operand size, distribution; + /// sketch-bench#147). Carried opaquely, like a synthetic workload + /// description; absent in tables written before it existed. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub measured_at: Option, } pub type AtomicCostTable = Vec; @@ -572,6 +578,23 @@ mod tests { assert!(serde_json::from_str::(json).is_err()); } + /// sketch-bench#147 adds `measured_at`; it loads, and older rows without + /// it still do. Other unknown fields are still refused. + #[test] + fn atomic_cost_entry_accepts_optional_measured_at() { + let base = r#""sketch":"kll-percall","sketch_config":null,"mem_bytes_per_instance":1.0,"insert_cpu_secs":1.0,"merge_cpu_secs":1.0,"query_cpu_secs":1.0,"query_accuracy":{}"#; + let with = format!( + r#"{{{base},"measured_at":{{"items_per_instance":1000000,"keys_per_instance":100000,"value_range":[1.0,100000.0],"merge_operand_items":62500,"distribution":{{"kind":"zipf","skewness":1.1,"population_size":100000,"seed":42}}}}}}"# + ); + let entry: AtomicCostEntry = serde_json::from_str(&with).unwrap(); + assert_eq!(entry.measured_at.unwrap()["items_per_instance"], 1_000_000); + let without: AtomicCostEntry = serde_json::from_str(&format!("{{{base}}}")).unwrap(); + assert!(without.measured_at.is_none()); + assert!( + serde_json::from_str::(&format!(r#"{{{base},"other":1}}"#)).is_err() + ); + } + #[test] fn optional_loader_rejects_an_unselected_document() { let document = tempfile::NamedTempFile::new().unwrap(); @@ -594,6 +617,7 @@ mod tests { merge_cpu_secs: 4.5e-4, query_cpu_secs: 7.8e-8, query_accuracy: BTreeMap::new(), + measured_at: None, } } @@ -616,6 +640,7 @@ mod tests { merge_cpu_secs: 4.0, query_cpu_secs: 8.0, query_accuracy: BTreeMap::new(), + measured_at: None, } } @@ -792,6 +817,7 @@ mod tests { merge_cpu_secs: 2.76e-4, query_cpu_secs: 1.23e-4, query_accuracy: BTreeMap::new(), + measured_at: None, }]; let hll_params = HashMap::from([("precision".to_string(), Value::from(14u64))]); assert!(resolve_atomic_costs(&hll_table, AggregationType::HLL, &hll_params, 0).is_some()); @@ -804,6 +830,7 @@ mod tests { merge_cpu_secs: 1.0e-3, query_cpu_secs: 1.6e-4, query_accuracy: BTreeMap::new(), + measured_at: None, }]; let kll_params = HashMap::from([("K".to_string(), Value::from(200u64))]); assert!( diff --git a/asap-planner-rs/src/optimizer/greedy.rs b/asap-planner-rs/src/optimizer/greedy.rs index dff2b44c..fa1bcb09 100644 --- a/asap-planner-rs/src/optimizer/greedy.rs +++ b/asap-planner-rs/src/optimizer/greedy.rs @@ -237,6 +237,7 @@ mod tests { merge_cpu_secs: 0.0, query_cpu_secs: 0.0, query_accuracy: std::collections::BTreeMap::new(), + measured_at: None, }]; let aqe = make_aqe(Statistic::Topk, 60_000, 60_000, 1.0 / 60.0); let solution = greedy_assign( @@ -275,6 +276,7 @@ mod tests { merge_cpu_secs: 0.0, query_cpu_secs: 0.0, query_accuracy: std::collections::BTreeMap::new(), + measured_at: None, }]; let mut aqe = make_aqe(Statistic::Sum, 60_000, 60_000, 1.0 / 60.0); aqe.requirements.grouping_labels = KeyByLabelNames::new(vec!["svc".into()]);