Skip to content

test(integration/parity): all-sketch e2e parity harness - #225

Merged
zzylol merged 3 commits into
mainfrom
phase2/parity-harness
May 2, 2026
Merged

zzylol merged 3 commits into
mainfrom
phase2/parity-harness

Conversation

@zzylol

@zzylol zzylol commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

End-to-end parity harness that proves wire-format invariance between the new asap-precompute-go runtime and the 5 legacy OTel sketch processors. This is the gate before Phase 2 steps 2.5–2.9 (refactoring each processor into a thin shim that delegates to the runtime).

The harness builds a deterministic multi-metric pmetric.Metrics payload covering all five sketch domains, feeds the same payload through both pipelines, and diffs the emitted SketchEnvelope payloads byte-for-byte.

Depends on

What the harness asserts

  • Payload bytes (SketchEnvelope.Payload) byte-identical for every (metric, resource-labels, dp-labels) tuple. This is the ADR-0002 §"Behavior preservation" invariant.
  • MetricName, Count, AggregationTemporality equal where the legacy path carries them.
  • Envelope count per (metric, label) tuple matches between the two paths.

When divergence is found, the diff reporter prints, per sketch type:

  • envelopes only in the runtime (Path A),
  • envelopes only in the legacy processor (Path B),
  • envelopes present in both with diverging payload bytes (with first-N bytes of each side).

How to run locally

cd integration/parity
go test -v ./...

The harness is fully synchronous: no goroutines, no wall-clock waits, no flake-prone timers. Two runs of the same harness produce byte-identical inputs and byte-identical outputs.

Results

Sketch Status Notes
DDSketch PASS 6 envelopes byte-identical between runtime and legacy.
KLL PASS 3 envelopes byte-identical (uses NewKLLSketchWithSeed(k, 42) from sketchlib-go #54 + new kllprocessor.Config.Seed knob; default Seed=nil keeps production behavior time-seeded).
HLL PASS 3 envelopes byte-identical (config: OmitResourceAttrs=true, runtime metric name <base>_hll_cardinality).
CountSketch PASS 1 envelope byte-identical (config: GlobalAggregation=true + new EmitWindowStats=true; runtime emits sample_count and window_duration_seconds natively, no diff-side attr strip).
CountMinSketch PASS 602 envelopes byte-identical (config: OmitResourceAttrs=true).

Closing the last two gaps in this PR

KLL — deterministic compaction seed

Until sketchlib-go #54, KLLSketch's compaction coin was seeded from time.Now().UnixNano() so the runtime and legacy processors built independent random streams. The fix exposes NewKLLSketchWithSeed(k, seed) and stores the seed so Clear() re-seeds deterministically across window rotations. This PR plumbs the seed through:

  • runtime path: harness.newKLLWrapper(k, HarnessKLLSeed) in integration/parity/harness/sketches.go.
  • legacy path: new kllprocessor.Config.Seed *int64 knob, set to &HarnessKLLSeed in harness/legacy.go. Production deployments leave Seed=nil and observe today's time-seeded behavior.

CountSketch — true byte-parity (no diff-side strip)

Previous form of this PR strip-projected sample_count and window_duration_seconds out of the legacy data point because the runtime didn't emit them. This PR adds PrecomputeConfig.EmitWindowStats (default false): when true, serializeSeries appends those two attrs onto the envelope's Labels at flush time, flowing through otel/encode.go::KeyValuesToAttributes to the output data point. Only the CountSketch sketch descriptor flips it on; the other four are unchanged so their parity is unaffected.

The harness's legacy WindowDuration is now set to cfg.WindowSize (was 24h) so the legacy and runtime stamp the same window_duration_seconds value — the test runs sub-second so the legacy ticker never fires before Shutdown.

What changed in this PR

  • asap-precompute-go/config.go — adds OmitResourceAttrs, GlobalAggregation, and EmitWindowStats flags to PrecomputeConfig, plus SeriesKeyFor(obs) / SeriesKeyForEntry(resourceLabels, labels) helpers (single call site for observe-time / flush-time).
  • asap-precompute-go/precompute.go::serializeSeries — appends sample_count and window_duration_seconds to envelope Labels when EmitWindowStats is set; uses cfg.SeriesKeyForEntry for snapshot-cache consistency.
  • asap-precompute-go/window.go — observe routes through the new helpers; seriesEntry strips ResourceLabels (and Labels under GlobalAggregation).
  • opentelemetry-collector-contrib-patch/processor/kllprocessor/config.go — new Seed *int64 config (default nil = time-based; only the parity harness sets it).
  • opentelemetry-collector-contrib-patch/processor/kllprocessor/processor.gonewKLLSketch accepts a *Config and dispatches to NewKLLSketchWithSeed when cfg.Seed != nil.
  • integration/parity/harness/runtime.goHarnessKLLSeed constant; per-sketch descriptor carries the config flags and now emitWindowStats.
  • integration/parity/harness/sketches.gonewKLLWrapper(k, seed) uses the seedable sketchlib-go constructor.
  • integration/parity/harness/legacy.go — sets kllproc.Config.Seed = &HarnessKLLSeed; aligns WindowDuration between CountSketch / CMS legacy and runtime.
  • integration/parity/harness/diff.go — drops the CountSketch attr-strip (still strips legacy KLL's kll.k operator-only attribute, which lives in the data point but not the runtime envelope).
  • integration/parity/parity_test.go — KLL is no longer SKIP; all 5 sketches must PASS.

Constraints honored

  • Determinism: every pseudorandom source uses a seeded rand.Source; KLL's compaction is now seeded too via the new sketchlib-go API.
  • No real network: both pipelines run in-process with synchronous consumertest.Sink consumers.
  • No flake tolerance: legacy processors run in batch mode (DDSketch, KLL, HLL) or in window mode with Shutdown-driven flush (CountSketch, CountMinSketch).
  • go test -race ./integration/parity/... clean; go test -count=3 ./... clean.
  • cd asap-precompute-go && go test ./... clean.
  • Submodule pointer drift on opentelemetry-collector and opentelemetry-go is intentionally not committed.
  • Only EmitWindowStats was added beyond the existing two flags (OmitResourceAttrs, GlobalAggregation).

What this PR does NOT do

  • Does not refactor any processor into a shim.
  • Does not modify the 5 legacy processors beyond the new optional Seed knob on kllprocessor.Config (production-default unchanged).

Test plan

  • go build ./... — clean.
  • cd integration/parity && go test -v ./... — 5 PASS, 0 SKIP.
  • cd integration/parity && go test -race ./... — clean.
  • cd integration/parity && go test -count=3 ./... — clean (no flake).
  • cd asap-precompute-go && go test ./... — clean.
  • cd opentelemetry-collector-contrib-patch/processor/kllprocessor && go test ./... — clean (existing tests still pass after the newKLLSketch(*Config) refactor).

zzylol and others added 3 commits May 2, 2026 10:16
…legacy processors

Builds a deterministic multi-metric pmetric.Metrics input covering all 5
sketch domains and feeds it through both asap-precompute-go (runtime) and
the legacy OTel processors. Compares emitted SketchEnvelopes byte-for-byte.

This is the gate before refactoring 5 processors into shims (Phase 2 steps
2.5–2.9). If parity holds, shim refactors become trivial; if not, it
surfaces runtime gaps before any production-shape change.

Results:
  DDSketch       PASS — 6 envelopes byte-identical between runtime and legacy
  KLL            SKIP — legacy batch path drops resource attrs from series
                        key and adds `_kll` metric-name suffix
  HLL            SKIP — legacy batch path drops resource attrs from series
                        key and adds `_hll_cardinality` metric-name suffix
  CountSketch    SKIP — legacy emits one global partition; runtime emits
                        per-(resource,labelset) series
  CountMinSketch SKIP — legacy keys series by (metricName, dp-attrs) only;
                        runtime always includes resource attrs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5 PASS

- Add OmitResourceAttrs and GlobalAggregation config knobs to honor
  legacy KLL/HLL/CMS/CountSketch series-key semantics. Defaults
  preserve today's resource-aware behavior; shims opt in.
- Route SeriesKey through PrecomputeConfig.SeriesKeyFor / -ForEntry
  so observe-time and flush-time agree on the bucket for a given
  config — single source of truth.
- Bake legacy metric-name suffix (`_kll`, `_hll_cardinality`,
  `countsketch_partition`) into the runtime's MetricName per
  sketch in the harness; no AdapterConfig.MetricSuffix needed.
- Strip CountSketch's operator-visibility attrs (`sample_count`,
  `window_duration_seconds`) from the diff comparison key — they
  are observability hints, not part of the routing key.
- Result: HLL, CountSketch, CountMinSketch flip to PASS;
  DDSketch unchanged. KLL remains SKIP for an out-of-scope
  cause: sketchlib-go's KLLSketch coin is seeded from
  `time.Now()` (sketches/KLL/kll.go::newCoin), so the runtime
  and legacy build independent sketches whose compaction
  patterns diverge regardless of input. Byte-parity for KLL
  needs a deterministic-seed API in sketchlib-go (separate PR).

Closes the gate before Phase 2 shim refactors (steps 2.5–2.9)
for the 4 deterministic-emit sketches; KLL gate is parked on
the sketchlib-go follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Plumb seedable KLL constructor through runtime wrapper and legacy
  processor (Config.Seed knob, default nil = time-based for production).
- Add EmitWindowStats config to runtime; CountSketch now emits
  sample_count and window_duration_seconds attrs natively.
- Drop diff-projection strip; CountSketch parity is now byte-identical.
- All 5 sketches PASS, no SKIPs.

Depends on: sketchlib-go#54

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 6b3258d into main May 2, 2026
@zzylol
zzylol deleted the phase2/parity-harness branch May 2, 2026 17:20
zzylol added a commit that referenced this pull request May 4, 2026
)

KLL processor reduces from ~720 LoC to ~120 LoC shim. State machine
moves to asap-precompute-go. sketch_wrapper.go implements QuantileSketch
over sketchlib-go KLL. Config.Seed (added in PR #225) flows through.

Public test API: Shim.ProcessBatch/ProcessMetrics/FlushWindow.

Parity harness: TestParity_KLL byte-identical (3 envelopes).

Phase 2 step 2.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