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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,30 @@ jobs:
env:
RUSTC_WRAPPER: sccache

# The viewer's Python tests run its JavaScript in V8 through py_mini_racer;
# the ones that plan a query run target/debug/stage_pipeline, which the
# workspace test build above already linked for devtools' tests.
# A pinned Python and Node: the runner's system Python refuses a global
# pip install (PEP 668).
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Node
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Run the DAG viewer's Python tests
working-directory: tools/dag-viewer
run: |
python3 -m pip install py-mini-racer==0.6.0
python3 -m unittest -v test_viewer test_server

- name: Run the DAG viewer's Node tests
run: node --test tools/dag-viewer/stages.test.mjs

# A PromQL-only runtime builds asap-planner without DataFusion or MetricsQL.
- name: Check and test asap-planner with the PromQL frontend alone
run: |
Expand Down
6 changes: 6 additions & 0 deletions crates/devtools/src/bin/stage_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,12 @@ mod tests {
use super::*;
use asap_devtools::planning_stages::run_planning_stages;

/// The exhaustive search's default `--max-combinations` is 10^4.
#[test]
fn exhaustive_default_candidate_limit_is_ten_thousand() {
assert_eq!(MAX_COMBINATIONS, 10_000);
}

/// With no valid candidate, the document is still written: `selected`
/// is null and every rejection is listed.
#[test]
Expand Down
17 changes: 0 additions & 17 deletions crates/devtools/tests/stage_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,23 +415,6 @@ fn nine_quantile_queries() -> Vec<String> {
args
}

/// The exhaustive search's default limit is 10^4: the nine quantile
/// queries' 19683 combinations are rejected once the count passes it, and
/// the message names the exhaustive search.
#[test]
fn exhaustive_default_candidate_limit_is_ten_thousand() {
let mut args = nine_quantile_queries();
args.push("--exhaustive".into());
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let (status, stderr) = failure(&args);
assert_eq!(status, Some(3));
assert!(
stderr.contains("workload has more than 10000 Stage 1 candidates"),
"{stderr}"
);
assert!(stderr.contains("--exhaustive"), "{stderr}");
}

/// Without `--exhaustive`, the same nine queries plan with the default
/// search, `plan_installable`'s: each query, reading its own metric, is an
/// independent component searched on its own, so no limit is reached. The
Expand Down
4 changes: 2 additions & 2 deletions crates/executor/tests/physical_dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ fn exact_state_and_family_validation() {

// Planner binding rejects unknown computation instead of accepting a fallback.
#[test]
fn bind_post_asap_before_execution() {
fn bind_physical_asap_dag_before_execution() {
use asap_executor::dag::planner::bind;
use planner_types::ir::properties::ExecutionDataState;
use planner_types::ir::scalar::{ArithmeticOpKind, ScalarValue};
Expand Down Expand Up @@ -567,7 +567,7 @@ fn bind_post_asap_before_execution() {
};
let native = bind(&dag, sources(), &[1]).unwrap();
assert_eq!(floats(&run(&native, 1, query()), 0), vec![3.]);
// A literal Fallback needs no deployment input.
// A literal `Values` source needs no deployment input.
let literal = bind(&dag, BTreeMap::new(), &[1]).unwrap();
assert_eq!(floats(&run(&literal, 1, query()), 0), vec![3.]);
dag.nodes[1].payload = PhysicalASAPOperatorPayload::ASAP(ASAPOp::Extension {
Expand Down
4 changes: 2 additions & 2 deletions crates/executor/tests/summary_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ use planner_types::ir::ASAPOp;
use planner_types::ir::NonASAPOp;
use std::{collections::BTreeMap, sync::Arc};

// A Post-ASAP projection may reorder/rename summary columns; recovery must retain
// A projection over a summary may reorder/rename its columns; recovery must retain
// the family and pass through the same immutable state, without decoding the payload.
#[test]
fn post_asap_summary_projection_survives_recovery() {
fn summary_projection_survives_recovery() {
let family = FieldDataType::ExactAggregate(ExactKind::Sum, ExactParams::Sum);
let schema = Arc::new(planner_types::ir::schema::Schema {
unique_keys: vec![],
Expand Down
6 changes: 4 additions & 2 deletions crates/executor/tests/weighted_topk_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ use planner_types::ir::schema::{DataType, *};
use planner_types::ir::ASAPOp;
use std::{collections::BTreeMap, rc::Rc, sync::Arc};
#[test]
#[ignore = "the whole-expression heap's SummaryEstimate{TopK} output is not the keyed-evaluation \
shape the physical planner builds (#579)"]
#[ignore = "the whole-expression heap keeps the outer top-k's keys indexed against the absorbed \
aggregate, so the physical planner rejects its keyed evaluation shape (#639); the \
unconfirmed Count-Min heap also has no non-negative weight proof until plan selection \
confirms one"]
fn planner_weighted_topk_binds_at_either_deployment_phase() {
assert_weighted_binding(SketchAlgorithm::CmsWithHeap);
assert_weighted_binding(SketchAlgorithm::CountSketchWithHeap);
Expand Down
1 change: 0 additions & 1 deletion crates/frontend-promql/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,5 @@ mod tests {
// must not hardcode "PromQL".
let msg = PromqlError::UnsupportedFeature("subquery".into()).to_string();
assert_eq!(msg, "unsupported feature: subquery");
assert!(!msg.contains("PromQL"), "got: {msg}");
}
}
8 changes: 4 additions & 4 deletions crates/frontend-promql/tests/count_planning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use asap_types::ir::schema::{
};
use asap_types::ir::{ASAPOp, Operator, OperatorNode};
use std::rc::Rc;
use support::{logical_candidates, lower_promql, post_asap_dag, selected_dag};
use support::{logical_candidates, lower_promql, physical_asap_dag, selected_dag};

/// Whether `node` reads a Hydra state (one sketch shared by every group).
fn reads_hydra(node: &Rc<OperatorNode>) -> bool {
Expand All @@ -24,7 +24,7 @@ fn reads_hydra(node: &Rc<OperatorNode>) -> bool {
}

#[test]
#[ignore = "Pass 1 offers no Hydra state for a grouped count yet (#605)"]
#[ignore = "Pass 1 offers no Hydra state for a grouped count yet (#703)"]
fn grouped_count_offers_hydra_but_selection_keeps_per_group_state() {
let target = AccuracyTarget::max_error(0.01).failure_probability(0.01);
// Hydra hashes a non-null item per row: the series identity (PromQL
Expand Down Expand Up @@ -99,7 +99,7 @@ fn frequency_count_candidates_use_unit_weights() {
) {
continue;
}
let dag = post_asap_dag(node);
let dag = physical_asap_dag(node);
assert!(
dag.nodes.iter().any(|node| matches!(
&node.payload,
Expand Down Expand Up @@ -233,7 +233,7 @@ fn cms_count_updates_total_ten_for_zero_positive_and_negative_samples() {
let dag = candidates
.iter()
.find_map(|node| {
let dag = post_asap_dag(node);
let dag = physical_asap_dag(node);
dag.nodes
.iter()
.any(|node| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn population_preserves_selector_horizon() {
panic!()
};
assert_eq!(spec.lookback_ms, 1_000);
support::post_asap_dag(&candidate);
support::physical_asap_dag(&candidate);
let NonASAPOp::Aggregate { child: source, .. } = root.expect_non_asap() else {
panic!()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use std::rc::Rc;

use asap_types::ir::operator::{AggIntent, BinaryOpKind, Reduction};
use asap_types::ir::properties::AccuracyTarget;
use asap_types::ir::scalar::{CompareOpKind, ScalarValue};
use asap_types::ir::{BinaryOperator, NonASAPOp, OperatorNode, ScalarExpr};
use asap_types::ir::scalar::CompareOpKind;
use asap_types::ir::{BinaryOperator, NonASAPOp, OperatorNode};
use support::lower_promql;

const CORPUS: &str = include_str!("data/awesome_prometheus_alerts.txt");
Expand Down Expand Up @@ -294,19 +294,6 @@ fn predict_linear_function_body_lowers_with_horizon() {
));
}

#[test]
fn vector_literal_lowers_to_a_labelless_vector() {
// `vector(1)` — used in dead-man's-switch ("always firing") alerts. Now
// lowers to a `PromqlVectorFromScalar` over the scalar `1` (issue #48).
let qe = ok("vector(1)");
let NonASAPOp::PromqlVectorFromScalar(inner) = qe.expect_non_asap() else {
panic!("expected PromqlVectorFromScalar, got {qe:?}");
};
assert!(matches!(inner, ScalarExpr::Literal(ScalarValue::Float64(v)) if *v == 1.0));
// The result is a vector: it carries a time index (unlike a bare scalar).
assert!(qe.schema.time_index.is_some());
}

#[test]
fn without_grouping_lowers_to_the_exclusion_form() {
// `(min without (cpu) (rate(node_cpu_seconds_total{mode="idle"}[1h]))) > 0.8`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,69 @@ mod support;
use asap_types::ir::properties::AccuracyTarget;
use support::{logical_plan, lower_promql};

const CORPORA: &[(&str, &str)] = &[
/// Today's coverage of one corpus, as a ratchet: a change that plans fewer
/// queries with an ASAP operator, or fails to plan more of them, fails the
/// test. Raise `min_asap_candidates` (or lower `max_planning_errors`) when
/// coverage improves.
struct CoverageRatchet {
/// Queries whose logical plan binds an ASAP operator below the root.
min_asap_candidates: usize,
/// Lowered queries whose logical planning returns an error.
max_planning_errors: usize,
}

const fn ratchet(min_asap_candidates: usize, max_planning_errors: usize) -> CoverageRatchet {
CoverageRatchet {
min_asap_candidates,
max_planning_errors,
}
}

const CORPORA: &[(&str, &str, CoverageRatchet)] = &[
(
"CMU query logs",
include_str!("data/metrics_observability/cmu_chad_logs.txt"),
ratchet(162, 0),
),
(
"CMU dashboards",
include_str!("data/metrics_observability/cmu_chad_dashboards.txt"),
ratchet(174, 0),
),
(
"CMU rules",
include_str!("data/metrics_observability/cmu_chad_rules.txt"),
ratchet(202, 0),
),
(
"Claude query logs",
include_str!("data/metrics_observability/from_claude_logs.txt"),
ratchet(71, 0),
),
(
"Grafana dashboards",
include_str!("data/metrics_observability/grafana_dashboards.txt"),
ratchet(164, 0),
),
(
"Kubernetes mixin dashboards",
include_str!("data/metrics_observability/kubernetes_mixin_dashboards.txt"),
ratchet(147, 0),
),
(
"Kubernetes mixin alerts",
include_str!("data/metrics_observability/kubernetes_mixin_alerts.txt"),
ratchet(27, 2),
),
(
"Kubernetes mixin rules",
include_str!("data/metrics_observability/kubernetes_mixin_rules.txt"),
ratchet(66, 0),
),
(
"Promset",
include_str!("data/metrics_observability/promset.txt"),
ratchet(1505, 4),
),
];

Expand All @@ -57,8 +84,8 @@ fn queries(corpus: &str) -> impl Iterator<Item = &str> {
}

#[test]
fn benchmark_corpora_are_total_and_report_coverage() {
for (name, corpus) in CORPORA {
fn benchmark_corpora_are_total_and_keep_their_coverage() {
for (name, corpus, ratchet) in CORPORA {
let mut total = 0;
let mut lowered = 0;
let mut parse_failures = 0;
Expand Down Expand Up @@ -103,6 +130,16 @@ fn benchmark_corpora_are_total_and_report_coverage() {
// counted above so extraction drift is visible, but are not expected
// to parse as PromQL.
assert_eq!(total, lowered + rejected + parse_failures);
assert!(
post_asap_candidates >= ratchet.min_asap_candidates,
"{name}: {post_asap_candidates} queries bind an ASAP operator, fewer than {}",
ratchet.min_asap_candidates
);
assert!(
post_asap_errors <= ratchet.max_planning_errors,
"{name}: {post_asap_errors} queries fail logical planning, more than {}",
ratchet.max_planning_errors
);
}
}

Expand Down
Loading
Loading