Skip to content

fix: SimpleEngine matchers for quantile_over_time and sum-instant queries - #108

Merged
zzylol merged 1 commit into
mainfrom
fix/warm-engine-quantile-and-sum-matchers
May 8, 2026
Merged

zzylol merged 1 commit into
mainfrom
fix/warm-engine-quantile-and-sum-matchers

Conversation

@zzylol

@zzylol zzylol commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Findings

The two query shapes are already covered by SimpleEngine's controller_patterns (engines/simple/engine.rs:213-343) and capability matching (asap-common/dependencies/rs/asap_types/src/capability_matching.rs:160-164 — DDSketch was added to Statistic::Quantile in PR #102). The 500/500 + 500/1000 demo failures are downstream of the matcher path and likely data-flow:

  1. The agent's DDSketch processor renames http_requests_total_latency_ms_latency_ms_quantile before backend ingest, so the unsuffixed PromQL query finds no data in the warm tier (capability-miss rather than pattern-miss).
  2. The warm tier ingests counters as IncreaseAccumulator, which doesn't implement Statistic::Sum. The bare sum by (zone) (counter) instant query asks for raw counter sums, not deltas, so compatible_agg_types(Sum) correctly rejects it. The matching sum by (zone) (rate(counter[5m])) succeeds because compatible_agg_types(Rate) matches [Increase, MultipleIncrease].

These data-flow gaps are out of scope for this PR. The regression tests here pin the matcher contract so a future refactor that breaks the working pattern paths surfaces in unit-test rather than 25-min-demo time.

Test plan

  • cargo test --release --lib -p query_engine_rust tests::datafusion::warm_engine_replay — all 3 new tests pass.
  • cargo test --release --test inference_yaml_pattern_coverage -p query_engine_rust — 11/11 pass (PR fix: warm-tier SimpleEngine answers canonical quantile_over_time + rate queries #102's regression suite still green).
  • cargo build --release — clean.
  • Pre-existing 33 lib-test failures unchanged (verified by stashing this PR's diff and re-running cargo test --release --lib: 894 pass with 33 fail on origin/main, 897 pass with 33 fail with this PR).

Refs: ProjectASAP/ASAPCollector#46

Failing-replay observation source: ProjectASAP/ASAPCollector issue #46 latest "Partial verdict — three improvements, two structural bugs surfaced" comment, ④ row.

🤖 Generated with Claude Code

…hapes

Adds three regression tests in
`asap-query-engine/src/tests/datafusion/warm_engine_replay_regression_tests.rs`
that pin the warm-tier `SimpleEngine` matcher + capability dispatch for
the two query shapes ProjectASAP/ASAPCollector#46 reported as
500/500 + 500/1000 `status=error` after the PR #333 demo run:

* `quantile_over_time(0.99, http_requests_total_latency_ms[1m])`
  against a `DDSketchAccumulator` — exercises `OnlyTemporal`
  pattern + `Statistic::Quantile` + capability matching now that
  PR #102 added `DDSketch` to `compatible_agg_types(Quantile)`.
* `quantile_over_time(0.5, ...)` variant of the above so the fix
  is not implicitly hard-coded to the 0.99 phi.
* `sum by (zone) (http_requests_total)` instant — exercises the
  `OnlySpatial` pattern + `Statistic::Sum` against a
  `SumAccumulator` window.

Tests use a windowed `(timestamp - 60s, timestamp)` insert (via an
inline `build_engine_with_window` helper) so the store's
`[query_start, query_end)` overlap filter selects them — the
`create_engine_single_pop` factory's zero-duration
`(timestamp, timestamp)` window misses that filter and produces a
spurious `No precomputed outputs found` for `OnlySpatial` queries
whose range is `[end - scrape*1000, end)`.

Audit conclusion: SimpleEngine's `controller_patterns`
(`engines/simple/engine.rs:213-343`) cover both query shapes
verbatim, and capability matching in
`asap-common/dependencies/rs/asap_types/src/capability_matching.rs:160-164`
already lists `DDSketch` for `Statistic::Quantile` (PR #102). No
matcher additions or fixes were needed; the demo's 500/500 +
500/1000 failures are downstream of the matcher path (likely
data-flow: the agent's DDSketch processor renames
`http_requests_total_latency_ms` → `_latency_ms_quantile` before
backend ingest, so the unsuffixed query name finds no data; and
the warm tier ingests counters as `IncreaseAccumulator`, which
does not support `Statistic::Sum` for the bare-counter spatial
sum). Those data-flow gaps are out of scope for this PR but the
regression tests here pin the matcher contract so a future
refactor that breaks the working pattern paths surfaces in
unit-test rather than 25-min-demo time.

Refs: ProjectASAP/ASAPCollector#46

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 4359e10 into main May 8, 2026
zzylol added a commit that referenced this pull request May 8, 2026
…unters now works) (#109)

Counters are ingested by the warm tier as `IncreaseAccumulator` (and
`MultipleIncreaseAccumulator` for keyed variants). Pre-fix neither
trait `query` answered `Statistic::Sum`, so an instant
`sum by (zone) (http_requests_total)` capability-missed even though
the matcher and OnlySpatial dispatch path were correct (PR #108
regression test pinned the matcher contract; the actual data-flow
gap is here).

Fix:
* `IncreaseAccumulator::query(Sum, ..)` returns
  `last_seen_measurement.value` — the latest cumulative counter
  value of that series, matching Prometheus' `sum(<counter>)`
  instant semantics.
* `MultipleIncreaseAccumulator::query` already delegates to the
  inner `IncreaseAccumulator`, so per-key Sum follows automatically.
* `compatible_agg_types(Statistic::Sum)` now lists `Increase` /
  `MultipleIncrease` so capability matching accepts counter-shaped
  configs.
* Updated the two unit tests that previously asserted Sum errors.
* Added a new warm-tier regression test
  (`sum_by_zone_instant_over_increase_accumulator_does_not_error`)
  that builds a `SimpleEngine` with two `IncreaseAccumulator`
  series under different zone labels and asserts
  `sum by (zone) (http_requests_total)` returns
  `QueryResult::Vector` with the per-zone latest cumulative values.

`rate(<counter>[5m])` and `increase(<counter>[5m])` are unaffected
— those statistics resolve to `Statistic::Rate` / `Statistic::Increase`
which already worked, and the changed match arm only adds a new
case for `Sum`.

Refs: ProjectASAP/ASAPCollector#46
Refs: PR #108 (warm-tier replay regression diagnosis)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 8, 2026
…PromQL queries

The agent's DDSketch processor renames raw input metrics
(`http_latency_ms`) to a sketched-form wire name
(`http_latency_ms_quantile`) before emitting to the warm tier — by
design, so warm-tier routing distinguishes raw from sketched form.
But the replay client / PromQL caller still references the
conceptual unsuffixed metric in `quantile_over_time(q, X[range])`,
so the warm engine sees a query for `X` while its sketch store
only holds `X_quantile`. Result: the lookup misses and the engine
returns `status=error` to a query that is logically answerable
(matchers + capability dispatch already work given the right name,
per PR #108).

This change picks Option 1 from the design alternatives — backend
self-heals stale routing tables — because it requires no controller
change and keeps the wire-side rename convention intact:

* New helper `resolve_quantile_metric_alias` in `SimpleEngine`
  parses the query, classifies its shape, and rewrites
  `<metric>` → `<metric>_quantile` only when (a) shape is
  `Quantile` (only shapes whose data lives behind the rename),
  (b) the bare metric is NOT registered locally but the suffixed
  variant IS, and (c) the rewrite re-parses cleanly. Deployments
  without the rename pass through unchanged.
* `handle_query_promql` calls the resolver once at entry so every
  downstream stage (pattern match, `QueryConfig` lookup, capability
  matching, `StoreQueryParams.metric`, schema label lookup) sees
  the same name.
* `replace_metric_token` does identifier-aware substitution
  ([A-Za-z0-9_:] boundary check) so a hypothetical
  `http_latency_ms_total` elsewhere in the query is left alone.

Three new regression tests in `warm_engine_replay_regression_tests`
pin the contract:

1. `quantile_over_time_resolves_unsuffixed_metric_to_quantile_state` —
   the production failure case from ProjectASAP/ASAPCollector#46:
   query `quantile_over_time(0.99, http_latency_ms[1m])` against a
   store that only holds `http_latency_ms_quantile` returns the
   DDSketch quantile, not `status=error`.
2. `non_quantile_query_does_not_rewrite_metric` — `sum_over_time`
   against the same store does NOT rewrite (shape gating).
3. `quantile_query_without_ingest_rename_passes_through` — when the
   bare metric IS registered locally (deployment without rename),
   the resolver leaves the query untouched.

Refs: ProjectASAP/ASAPCollector#46
Builds on: PR #102 (DDSketch in `compatible_agg_types(Quantile)`),
PR #108 (warm-tier matcher coverage tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 8, 2026
…PromQL queries (#110)

The agent's DDSketch processor renames raw input metrics
(`http_latency_ms`) to a sketched-form wire name
(`http_latency_ms_quantile`) before emitting to the warm tier — by
design, so warm-tier routing distinguishes raw from sketched form.
But the replay client / PromQL caller still references the
conceptual unsuffixed metric in `quantile_over_time(q, X[range])`,
so the warm engine sees a query for `X` while its sketch store
only holds `X_quantile`. Result: the lookup misses and the engine
returns `status=error` to a query that is logically answerable
(matchers + capability dispatch already work given the right name,
per PR #108).

This change picks Option 1 from the design alternatives — backend
self-heals stale routing tables — because it requires no controller
change and keeps the wire-side rename convention intact:

* New helper `resolve_quantile_metric_alias` in `SimpleEngine`
  parses the query, classifies its shape, and rewrites
  `<metric>` → `<metric>_quantile` only when (a) shape is
  `Quantile` (only shapes whose data lives behind the rename),
  (b) the bare metric is NOT registered locally but the suffixed
  variant IS, and (c) the rewrite re-parses cleanly. Deployments
  without the rename pass through unchanged.
* `handle_query_promql` calls the resolver once at entry so every
  downstream stage (pattern match, `QueryConfig` lookup, capability
  matching, `StoreQueryParams.metric`, schema label lookup) sees
  the same name.
* `replace_metric_token` does identifier-aware substitution
  ([A-Za-z0-9_:] boundary check) so a hypothetical
  `http_latency_ms_total` elsewhere in the query is left alone.

Three new regression tests in `warm_engine_replay_regression_tests`
pin the contract:

1. `quantile_over_time_resolves_unsuffixed_metric_to_quantile_state` —
   the production failure case from ProjectASAP/ASAPCollector#46:
   query `quantile_over_time(0.99, http_latency_ms[1m])` against a
   store that only holds `http_latency_ms_quantile` returns the
   DDSketch quantile, not `status=error`.
2. `non_quantile_query_does_not_rewrite_metric` — `sum_over_time`
   against the same store does NOT rewrite (shape gating).
3. `quantile_query_without_ingest_rename_passes_through` — when the
   bare metric IS registered locally (deployment without rename),
   the resolver leaves the query untouched.

Refs: ProjectASAP/ASAPCollector#46
Builds on: PR #102 (DDSketch in `compatible_agg_types(Quantile)`),
PR #108 (warm-tier matcher coverage tests).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the fix/warm-engine-quantile-and-sum-matchers branch May 9, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant