Skip to content

feat(e2e): all-five-sketch runtime path + harness — controller, processor, fake-exporter, P1–P9 - #204

Merged
zzylol merged 1 commit into
mainfrom
feat/e2e-all-five-sketches
May 1, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/e2e-all-five-sketches

Conversation

@zzylol

@zzylol zzylol commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Lands the real e2e: PromQL → controller → agent (sketchcol) → backend (precompute_engine) → PromQL response, through the modified-OTLP wire format (typed Metric.data = {DDSketch | KLLSketch | HLLSketch | CountSketch | CountMinSketch}). Each sketch type now has a runtime-verified path with a soak against its accuracy envelope; the P1–P9 harness drives the full sweep matrix.

Sketch Query Result Envelope
DDSketch histogram_quantile(0.5/0.9/0.99,…) 21.12 / 47.95 / 104.60 relative_quantile, ε=0.01
KLLSketch histogram_quantile(0.5,…) 18.26 rank_quantile, ε=0.16
HLLSketch count(http_requests_total) 149.68 distinct relative_cardinality, ε=0.008
CountSketch sum_over_time(http_requests_total[1m]) 24266 additive_frequency, ε=0.03
CountMinSketch sum_over_time(http_requests_total[1m]) 145735 additive_frequency, ε≈0.0027, δ=0.03125

Cross-cutting fixes

  • Controller data_sink (types.rs, config/agent.rs): AgentDataSink enum picks between Otlp{endpoint,…} and PrometheusScrape{…} instead of always emitting prometheus.
  • OpAMP framing (opamp/mod.rs): strip varint header from incoming WS payloads before proto decode; outbound ServerToAgent carries ReportFullState + Accept/Offer capability bitmask so agents accept and apply config.
  • ddsketchprocessor: replaced DataDog sketches-go with sketchlib-go/DDSketch so the proto envelope is decodable by asap_sketchlib's DDSketchState.
  • Sketchcollector builder: builder-config-sketches.yaml (renamed from builder-config-ddonly.yaml) compiles all five sketch processors plus opampextension under OCB v0.141.0.

Harness (P1–P9)

  • P2 deploy/fake-exporter/swappable_filter.go — atomic.Pointer-backed Stream.AttributeFilter, swap via POST /control/projection. No SDK patch needed.
  • P4 deploy/fake-exporter/raw_tee.go — hour-bucketed JSONL ground-truth writer, byte-for-byte compatible with the Rust RawSample wire format. Wired into runSynthetic + runTraceReplay.
  • P5 deploy/scripts/promql_replay.py — fixed-QPS PromQL fan-out, plan-id-tagged JSONL.
  • P6 deploy/scripts/plan_transition.pyt_query_in / t_plan_ready / t_first_hit / t_steady against the controller plan-id stream + 1 Hz docker stats.
  • P7 deploy/scripts/run_e2e_sweep.sh{DDSketch,KLL,CS,CMS,HLL} × {N=1,10} × {scrape=100ms,1s} × {card=1e3,1e4,1e5} (60 cells).
  • P8 deploy/scripts/accuracy_reduce.py — cold-truth ⋈ replay → relative-error CSV.
  • P9 deploy/scripts/e2e_plots.py — pareto / bandwidth / transition / latency-CDF figures.

Known limitation

Stock OTel gateway 0.108 can't translate DDSketch / HLLSketch through prometheusremotewrite, so warm-tier sketch ingest is dropped at the gateway — the cold-tier path (P1+P4) carries the e2e flow today. Tracked in PROGRESS.md follow-ups: enable OTLP ingest on precompute_engine (or swap the backend image to query_engine_rust, which already has it).

Companion PR in ASAPQuery-backend lands the matching query_statistic impls + --cold-store-root wiring.

Test plan

  • All five sketches: live PromQL → expected accuracy envelope (table above)
  • docker compose -f base.yml -f agents-N1.yml -f baseline-b3-delta.yml -f e2e-overlay.yml up -d brings the stack up cleanly
  • swappable_filter HTTP swap returns {"applied":"zone"}
  • raw_tee writes ground-truth JSONL with the right path layout (8 unit tests incl. concurrent-writer race + format anchor)
  • P5 smoke: 17 queries / 6 s, p50 2.4 ms, p99 1.3 s
  • P8 smoke: 4.1 M ground-truth samples, 17 query rows, accuracy CSV produced
  • P9 smoke: pareto_acc_vs_thru.png + query_latency_cdf.png from real data
  • Full P7 sweep over 60 cells (hours wall) — separate run
  • OTel submodule bumps deferred (excluded from this PR)

🤖 Generated with Claude Code

…ssor, fake-exporter, P1–P9

Lands the real e2e: PromQL → controller → agent (sketchcol) → backend
(precompute_engine) → PromQL response, through the modified-OTLP wire
format (typed Metric.data = {DDSketch | KLLSketch | HLLSketch |
CountSketch | CountMinSketch}). One soak per sketch verified against
its accuracy envelope; P1–P9 harness drives the full sweep matrix.

Controller / data sink:
- AgentDataSink enum (Otlp{endpoint,...} vs PrometheusScrape{...})
  in types.rs; agent.rs picks the exporter at config-gen time
  instead of always emitting `prometheus`.
- OpAMP: strip varint header from incoming WS frames before proto
  decode; outbound ServerToAgent carries ReportFullState +
  Accept/Offer capability bitmask so agents accept and apply config.

Sketchcollector / processor:
- ddsketchprocessor: replace DataDog sketches-go with sketchlib-go
  DDSketch so the proto envelope is decodable by asap_sketchlib's
  DDSketchState.
- builder-config-sketches.yaml (renamed from builder-config-ddonly):
  compiles all five sketch processors plus opampextension under
  OCB v0.141.0.

Fake-exporter:
- swappable_filter.go (P2): atomic.Pointer-backed Stream.AttributeFilter
  wired through `POST /control/projection`; SDK needed no patch.
- raw_tee.go (P4): hour-bucketed JSONL writer matching the Rust
  RawSample wire format byte-for-byte; mounted at EXPORTER_RAW_TEE_ROOT.
- main.go: wired into runSynthetic + runTraceReplay.

Harness (P5–P9):
- promql_replay.py: PromQL fan-out at fixed QPS, plan-id-tagged JSONL.
- plan_transition.py: t_query_in / t_plan_ready / t_first_hit /
  t_steady against the controller plan-id stream + 1 Hz docker stats.
- run_e2e_sweep.sh: {DDSketch,KLL,CS,CMS,HLL} × {N=1,10} × {scrape=
  100ms,1s} × {card=1e3,1e4,1e5} = 60 cells.
- accuracy_reduce.py: cold-truth ⋈ replay → relative error per row.
- e2e_plots.py: pareto / bandwidth / transition / latency CDF.

Configs / overlays:
- New per-sketch overlays (e2e-overlay-{cms,cs,hll,kll}.yml + base
  e2e-overlay.yml) and per-sketch agent + backend configs
  (sketchcol-agent-*-direct.yaml, backend-streaming-*.yaml,
  backend-inference-*.yaml).

Known limitation: stock OTel gateway 0.108 can't translate
DDSketch/HLLSketch through prometheusremotewrite, so warm-tier
sketch ingest is dropped at the gateway — the cold-tier path
(P1+P4) carries the e2e flow through. Fix is to enable OTLP ingest
on precompute_engine or swap the backend image to query_engine_rust.
Tracked in PROGRESS.md follow-ups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 8b0fb3a into main May 1, 2026
@zzylol
zzylol deleted the feat/e2e-all-five-sketches branch May 1, 2026 03:26
zzylol added a commit to ProjectASAP/ASAPQuery-backend that referenced this pull request May 1, 2026
Lands the backend half of the runtime e2e: each sketch type now has
a verified PromQL → backend path through the modified-OTLP wire
format (typed Metric.data = {DDSketch | KLLSketch | HLLSketch |
CountSketch | CountMinSketch}).

query_statistic implementations:
- DDSketchAccumulator: Quantile / Sum / Count / Min / Max.
- HllSketchAccumulator: Cardinality, with Count accepted as a
  Cardinality alias for the existing PromQL count(...) path.
- CountSketchAccumulator: Topk / Count / Sum, no-key fallback
  returns row-mean total.
- CountMinSketchAccumulator: Count / Sum, no-key fallback returns
  the min-row sum — canonical CMS total-event estimator that's
  exact when each insert increments one cell per row (and CMS
  never under-counts).

accumulator_factory.rs: DDSketchAccumulatorUpdater wired in
alongside CMS / CountSketch / KLL / HLL updaters so the
precompute_engine recognises AggregationType::DDSketch from
streaming.yaml.

Modified-OTLP envelope decoders go through
from_sketchlib_proto_bytes / from_msgpack_bytes against
SketchEnvelope { sketch_state: Some(SketchState::*(state)) }.

P1 — cold-store flag wiring (asap-query-engine/main.rs):
- --cold-store-root with env = "ASAP_COLD_STORE_ROOT" plumbed
  through a build_adapter_config helper that selects
  prometheus_promql_with_cold when set.
- Combine with --forward-unsupported-queries to keep Prom as the
  tail of the chain.
- Four unit tests pin the wiring matrix (cold × forward).

Companion PR in ASAPCollector lands the agent + harness
(see ProjectASAP/ASAPCollector#204).

Known cleanup (not a blocker): compatible_agg_types in
capability_matching.rs doesn't list CountMinSketch under
Statistic::Sum, but query_logics treats CMS as the canonical
approximator for both Sum and Count. The exact-match
find_query_config path bypasses capability matching, so the
runtime e2e succeeds today; reconciling the two tables is a
separate change.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 6, 2026
…pr + Schema flow) (#275)

Per design.md §6 "core::intent_algebra — Layer 3", introduce the L3
intent IR the planner pivots on, in a new `controller/src/intent_algebra/`
module. Phase A (#273) shipped the typed QuerySpec types; this PR ships
the algebra + schema flow they describe a workload over.

What lands:

- `AggIntent` — vocabulary of "what to compute, not how": Count, Sum,
  Min, Max, Avg, Quantile{q, accuracy}, TopK{k, accuracy},
  Cardinality{accuracy}, Frequency{accuracy}, Rate{window},
  Increase{window}. No sketch types here — sketch binding is L4.
- `QueryExpr` — the L3 algebra DAG, single-rooted per query: Scan,
  Window, Aggregate, LetBinding, Ref. Variant subset chosen for the DC
  + PromQL deployment scope per the orchestrator spec; Filter/Project/
  Partition/Distinct/Merge/Join/SetOp/Sort/Limit/Subquery/WindowFunc/
  BinaryOp are deferred so each lands with a planner consumer rather
  than as dead code (additive growth).
- `Schema` — typed schema flowing on every L3 edge with `unique_keys`
  populated per design.md §6 schema-flow table. `unique_keys` is the
  load-bearing CSE-legality field (design.md §6 line ~1284 + the
  batched-queries example); `cse_substitution_legal_only_with_unique_keys`
  pins this invariant as a unit test.
- `lower_parsed_query(parsed, accuracy) → QueryExpr` — single-query
  lowering from the existing `query_parser::ParsedQuery`. Workload-
  level CSE that produces fan-in (multi-root with LetBinding/Ref) is
  the follow-up's job.

Per-variant schema propagation rules match the design.md table: Scan
emits the source schema with unique_keys from its catalog; Window
propagates row identity (carries unique_keys verbatim, requires
time_index on input); Aggregate{by, ..} emits unique_keys = [by] and
strips the time axis; LetBinding/Ref propagate the bound expr's schema.

Wire-up state. Nothing in `analyzer::Analyzer` or `planner/` consumes
these types yet — that's the follow-up Phase C PR. Phase B exposes the
IR so that wiring becomes a focused change rather than co-emission of
new types + new consumers.

Tests added (in module): agg_intent_serde_roundtrip,
output_column_names_are_intent_keyed, quantile_output_is_float64,
sum_preserves_input_dtype, schema_serde_roundtrip, schema_new_*,
schema_with_time_index_*, add_unique_key_dedupes,
query_expr_simple_aggregate, query_expr_let_binding_ref,
query_expr_unresolved_ref_errors, query_expr_window_requires_time_index,
query_expr_aggregate_invalid_by_column, query_expr_serde_roundtrip,
cse_substitution_legal_only_with_unique_keys, lower_promql_basic,
lower_promql_with_group_by, lower_promql_cardinality,
lower_empty_metric_errors.

design.md §6 grows an "Implementation status" subsection naming the
Phase B subset and the deferred phases C/D/E/F.

Note on cargo test status. The controller's `cargo test` target was
already broken at origin/main (post-#273): nine `data_sink` field-init
errors in `src/config/agent.rs`, `src/config/asapquery_backend.rs`,
`src/config/precompute.rs`, and `src/main.rs` test code. This is
pre-existing breakage from the `data_sink` field added to
`AgentCollectorConfig` in #204; intent_algebra introduces no new test
errors. `cargo build` and `cargo clippy --bins` are both clean for
intent_algebra.

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

PR #204 added the `data_sink: AgentDataSink` field to
`AgentCollectorConfig` (production sites updated) but missed nine
test-fixture / test-code constructors, leaving
`cargo test --release -p controller` blocked at compile-time. This
broke the baseline for downstream PRs (#273, #274, #275) which could
not run their newly added unit tests.

Mechanical fill-in only, no semantic change to existing tests:
- 7 sites use `AgentDataSink::default()` (Otlp-to-backend) — the
  canonical default that PR #204 introduced for new pipelines.
- 2 sites (`config::agent::tests::ddsketch_cfg` and
  `main::api_tests::generated_agent_yaml_contains_opamp_extension`)
  pin `AgentDataSink::PrometheusScrape { endpoint: "0.0.0.0:8889" }`
  because their pre-existing assertions check for the legacy
  `prometheus` exporter on :8889. Pinning the sink keeps the test
  semantics intact rather than rewriting the asserts.

After the fix:
- `cargo build --release -p controller` clean.
- `cargo test --release -p controller --no-run` clean (was the
  blocker).
- `cargo test --release -p controller` runs 395 tests; 389 pass,
  6 pre-existing failures unrelated to `data_sink`:
    * 2 in `analyzer::tests` — float-precision asserts.
    * 4 in `opamp::tests` / `api_tests` — protobuf framing
      ("invalid tag value: 0") on `ServerToAgent` decode.
  These are tracked separately and out of scope for this PR.
- Newly-shipped tests now run end-to-end:
  `query_language` (8), `language_logical_plan` (6),
  `types_v2` (7), `algebra` (intent_algebra family, 6+).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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