Skip to content

Commit dcdd2c5

Browse files
Merge remote-tracking branch 'origin/main' into 790-a5p-planner-flag
# Conflicts: # asap-planner-rs/src/bin/optimizer_cli.rs # asap-planner-rs/src/optimizer/milp.rs
2 parents 0c4bcf5 + 847bf1e commit dcdd2c5

7 files changed

Lines changed: 518 additions & 73 deletions

File tree

‎Cargo.lock‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎asap-planner-rs/src/bin/optimizer_cli.rs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ struct Args {
2525
#[arg(long = "data-ingestion-interval-ms", value_parser = clap::value_parser!(u64).range(1..))]
2626
data_ingestion_interval_ms: u64,
2727

28-
/// The flat cost table `export_rqe_optimizer_costs.sh` writes
28+
/// The flat cost table sketch-bench's
29+
/// `study_saturation.py --phase optimizer-cost` writes
2930
/// (`rqe_atomic_costs.json`).
3031
#[arg(long = "atomic-costs")]
3132
atomic_costs: PathBuf,
@@ -41,10 +42,17 @@ struct Args {
4142
allow_undeployable_families: bool,
4243

4344
/// YAML workload facts: per metric, positive `value_range` and
44-
/// `cardinality` per label set, including all labels (the series count).
45+
/// `cardinality` per label set, including all labels (the series count),
46+
/// plus the `shape` of each grouping sketches may serve.
4547
#[arg(long = "workload-facts")]
4648
workload_facts: PathBuf,
4749

50+
/// sketch-bench's saturation-study directory (`out_grid_1e7_cost/`,
51+
/// `out_1e9/`): sketch accuracy is read off its error-vs-N curves at each
52+
/// grouping's `shape`.
53+
#[arg(long = "saturation-dir")]
54+
saturation_dir: PathBuf,
55+
4856
/// Objective weight on CPU-sec/sec. Default: rqe-optimizer's.
4957
#[arg(long = "w-cpu", value_parser = parse_weight)]
5058
w_cpu: Option<f64>,
@@ -87,6 +95,7 @@ fn run_milp(args: &Args, config: &ControllerConfig) -> anyhow::Result<()> {
8795
&MilpInputs {
8896
workload_facts: &args.workload_facts,
8997
atomic_costs: &args.atomic_costs,
98+
saturation_dir: &args.saturation_dir,
9099
scrape_interval_ms: args.data_ingestion_interval_ms,
91100
w_cpu: args.w_cpu,
92101
w_mem: args.w_mem,

‎asap-planner-rs/src/main.rs‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,22 @@ struct Args {
6262
planner: PlannerArg,
6363

6464
/// MILP only. YAML workload facts: per metric, positive `value_range` and
65-
/// `cardinality` per label set, including all labels (the series count).
65+
/// `cardinality` per label set, including all labels (the series count),
66+
/// plus the `shape` of each grouping sketches may serve.
6667
#[arg(long = "workload-facts", required_if_eq("planner", "milp"))]
6768
workload_facts: Option<PathBuf>,
6869

69-
/// MILP only. The flat cost table `export_rqe_optimizer_costs.sh` writes
70+
/// MILP only. The flat cost table sketch-bench's
71+
/// `study_saturation.py --phase optimizer-cost` writes
7072
/// (`rqe_atomic_costs.json`).
7173
#[arg(long = "atomic-costs", required_if_eq("planner", "milp"))]
7274
atomic_costs: Option<PathBuf>,
7375

76+
/// MILP only. sketch-bench's saturation-study directory: sketch accuracy
77+
/// is read off its error-vs-N curves at each grouping's `shape`.
78+
#[arg(long = "saturation-dir", required_if_eq("planner", "milp"))]
79+
saturation_dir: Option<PathBuf>,
80+
7481
/// MILP only. Objective weight on CPU-sec/sec. Default: rqe-optimizer's.
7582
#[arg(long = "w-cpu", value_parser = parse_weight)]
7683
w_cpu: Option<f64>,
@@ -117,9 +124,11 @@ fn main() -> anyhow::Result<()> {
117124
anyhow::ensure!(
118125
args.workload_facts.is_none()
119126
&& args.atomic_costs.is_none()
127+
&& args.saturation_dir.is_none()
120128
&& args.w_cpu.is_none()
121129
&& args.w_mem.is_none(),
122-
"--workload-facts, --atomic-costs, --w-cpu and --w-mem require --planner milp"
130+
"--workload-facts, --atomic-costs, --saturation-dir, --w-cpu and --w-mem require \
131+
--planner milp"
123132
);
124133

125134
match args.query_language {
@@ -251,6 +260,10 @@ fn run_milp(args: &Args) -> anyhow::Result<()> {
251260
.atomic_costs
252261
.as_deref()
253262
.expect("clap requires --atomic-costs with --planner milp"),
263+
saturation_dir: args
264+
.saturation_dir
265+
.as_deref()
266+
.expect("clap requires --saturation-dir with --planner milp"),
254267
scrape_interval_ms,
255268
w_cpu: args.w_cpu,
256269
w_mem: args.w_mem,

‎asap-planner-rs/src/optimizer/atomic_costs.rs‎

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
//! The flat atomic-cost table sketch-bench's `export_rqe_optimizer_costs.sh`
2-
//! writes for the MILP.
1+
//! The flat atomic-cost table sketch-bench's
2+
//! `study_saturation.py --phase optimizer-cost` writes for the MILP.
33
44
use std::path::Path;
55

66
pub use rqe_optimizer::{AtomicCostEntry, AtomicCostTable};
77

8-
/// Load the flat cost table `export_rqe_optimizer_costs.sh` writes, for the
9-
/// MILP. An invalid row is an error, not dropped.
8+
/// Load the flat cost table `study_saturation.py --phase optimizer-cost`
9+
/// writes, for the MILP. An invalid row is an error, not dropped.
1010
pub fn load_flat_atomic_cost_table(path: &Path) -> anyhow::Result<AtomicCostTable> {
1111
let raw = std::fs::read_to_string(path)
1212
.map_err(|e| anyhow::anyhow!("reading cost table {}: {e}", path.display()))?;
@@ -37,6 +37,21 @@ fn valid_cost_entry(entry: &AtomicCostEntry) -> bool {
3737
.all(|cost| cost.is_finite() && *cost >= 0.0)
3838
}
3939

40+
/// A cost row's required `measured_at`, for test fixtures: the cost table's
41+
/// shape (sketch-bench `study_saturation.py` COST_*). Generic so the
42+
/// fixture needn't name `aqpbm_core::MeasuredAt`.
43+
#[cfg(test)]
44+
pub(crate) fn test_measured_at<T: serde::de::DeserializeOwned>() -> T {
45+
serde_json::from_value(serde_json::json!({
46+
"items_per_instance": 1_000_000,
47+
"keys_per_instance": 10_000,
48+
"value_range": null,
49+
"merge_operand_items": null,
50+
"distribution": null,
51+
}))
52+
.expect("MeasuredAt fixture")
53+
}
54+
4055
#[cfg(test)]
4156
mod tests {
4257
use super::*;
@@ -51,7 +66,7 @@ mod tests {
5166
fn flat_loader_rejects_non_finite_or_negative_costs() {
5267
let row = |insert: &str| {
5368
format!(
54-
r#"{{"sketch":"hll","sketch_config":null,"mem_bytes_per_instance":1.0,"insert_cpu_secs":{insert},"merge_cpu_secs":1.0,"query_cpu_secs":1.0,"query_accuracy":{{}}}}"#
69+
r#"{{"sketch":"hll","sketch_config":null,"mem_bytes_per_instance":1.0,"insert_cpu_secs":{insert},"merge_cpu_secs":1.0,"query_cpu_secs":1.0,"query_accuracy":{{"relative_error":0.0}},"accuracy_metric":"relative_error","measured_at":{{"items_per_instance":1000000,"keys_per_instance":10000,"value_range":null,"merge_operand_items":null,"distribution":null}}}}"#
5570
)
5671
};
5772
let file = tempfile::NamedTempFile::new().unwrap();
@@ -62,16 +77,21 @@ mod tests {
6277
assert!(err.to_string().contains("negative"), "{err}");
6378
}
6479

65-
/// Rows with `measured_at` load, and older rows without it still do.
80+
/// Rows name their accuracy metric and where they were measured; a row
81+
/// without either is an old table, rejected rather than guessed.
6682
#[test]
67-
fn atomic_cost_entry_accepts_optional_measured_at() {
68-
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":{}"#;
69-
let with = format!(
70-
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}}}}}}"#
71-
);
72-
let entry: AtomicCostEntry = serde_json::from_str(&with).unwrap();
73-
assert_eq!(entry.measured_at.unwrap().items_per_instance, 1_000_000);
74-
let without: AtomicCostEntry = serde_json::from_str(&format!("{{{base}}}")).unwrap();
75-
assert!(without.measured_at.is_none());
83+
fn atomic_cost_entry_requires_accuracy_metric_and_measured_at() {
84+
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":{"mean_rank_err":0.01}"#;
85+
let measured_at = r#""measured_at":{"items_per_instance":1000000,"keys_per_instance":null,"value_range":null,"merge_operand_items":62500,"distribution":{"kind":"pareto","alpha":2.0,"scale":1000.0,"seed":42}}"#;
86+
let full = format!(r#"{{{base},"accuracy_metric":"mean_rank_err",{measured_at}}}"#);
87+
let entry: AtomicCostEntry = serde_json::from_str(&full).unwrap();
88+
assert_eq!(entry.measured_at.items_per_instance, 1_000_000);
89+
assert_eq!(entry.accuracy(), Some(0.01));
90+
for partial in [
91+
format!(r#"{{{base},{measured_at}}}"#),
92+
format!(r#"{{{base},"accuracy_metric":"mean_rank_err"}}"#),
93+
] {
94+
assert!(serde_json::from_str::<AtomicCostEntry>(&partial).is_err());
95+
}
7696
}
7797
}

0 commit comments

Comments
 (0)