Skip to content

feat(workload): fake-exporter trace-replay mode + Google-trace-shaped demo - #182

Merged
zzylol merged 1 commit into
mainfrom
feat/trace-replay-workload
Apr 22, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/trace-replay-workload

Conversation

@zzylol

@zzylol zzylol commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Adds CSV-driven trace replay to fake-exporter. EXPORTER_TRACE_FILE enables the new mode; synthetic log-normal remains the default.

Schema (3 cols, header required):

timestamp_ms,series_id,value
1700000000000,instance-0000,0.253907

Google 2019 cluster-trace instance_usage preprocesses into this schema with a column-rename. See deploy/fake-exporter/traces/README.md for the mapping.

Bundled: demo-trace.csv + gen-demo-trace.py generator. 100 series × 600 samples = 10-minute loop (~2 MiB). Shaped to resemble real cluster-trace CPU — slow drift + sparse spikes + rare flatlines + inter-instance diurnal correlation. Deterministic seed for reproducibility.

Compose overlay trace-replay.yml swaps workload mode on top of any baseline: docker compose -f base.yml -f agents-N1.yml -f baseline-b3-delta.yml -f trace-replay.yml up -d.

Binary smoke-tested: loads 60k rows / 100 series in ~20ms, ready to emit.

Follow-up: live sweep against demo-trace across all baselines + preprocess a real Google cluster trace slice — both eval-results PRs.

🤖 Generated with Claude Code

Adds a second operating mode to fake-exporter: when
EXPORTER_TRACE_FILE is set it reads a CSV of
`(timestamp_ms, series_id, value)` rows and emits them as OTLP
gauges at the recorded pace. The paper §6.1 workload-credibility
hook — baselines can now run against real production data
instead of the synthetic log-normal default.

CSV schema (header required):

    timestamp_ms,series_id,value
    1700000000000,instance-0000,0.253907

Preprocessed Google 2019 cluster-trace data (instance_usage
CPU column) fits this schema with a trivial column-rename; see
`deploy/fake-exporter/traces/README.md` for the mapping.

Bundled: `traces/demo-trace.csv` + `gen-demo-trace.py` that
generates it. 100 series × 600 samples = 10-minute loop,
~2 MiB. Statistically shaped to resemble real cluster-trace
CPU: per-instance slow drift + sparse spikes + rare flatlines
+ inter-instance diurnal correlation. Deterministic via
`PYTHONHASHSEED` + argv seed for reproducibility.

New compose overlay `trace-replay.yml` — compose it on top of
any baseline overlay to swap workload mode:

    EXPORTER_TRACE_FILE=/trace/demo-trace.csv \
      docker compose -f base.yml -f agents-N1.yml \
        -f baseline-b3-delta.yml -f trace-replay.yml up -d

Additional trace-mode env knobs:
  * `EXPORTER_TRACE_SCALE` (default 1.0) — playback-speed
    multiplier. 10× collapses a 1-hour trace into 6 minutes.
  * `EXPORTER_TRACE_LOOP` (default true) — wrap at EOF for
    long soaks.

Binary smoke-test:

    docker run --rm \
      -v .../traces:/trace:ro \
      -e EXPORTER_TARGET=localhost:1 \
      -e EXPORTER_TRACE_FILE=/trace/demo-trace.csv \
      -e EXPORTER_TRACE_LOOP=false \
      asap/fake-exporter:dev

  fake-exporter starting (trace replay):
    metric=http_requests_total_trace rows=60000 series=100
    scale=1.00x loop=false

Synthetic mode is unchanged and remains the default when
`EXPORTER_TRACE_FILE` is unset — every existing baseline sweep
keeps working byte-for-byte.

Not live-validated end-to-end in this PR: the full B3/B4 delta
win on the low-churn trace workload (the whole motivation) is
a sweep that needs to land as a separate eval-results PR,
probably once the real Google trace slice is preprocessed and
dropped in alongside demo-trace.csv.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 63b97a7 into main Apr 22, 2026
@zzylol
zzylol deleted the feat/trace-replay-workload branch April 22, 2026 15:24
zzylol added a commit that referenced this pull request Apr 23, 2026
* docs: RCA — "N=10 throughput collapse" is not a bottleneck

PR #185 flagged a universal ~2k pts/s floor at N=10 across all
six baselines and attributed it to a coordinated throttle
(OTLP SDK / Docker userland-proxy / kernel socket buffers).

Rate-invariance test disproves the bottleneck reading. Holding
cardinality=1000 and varying EXPORTER_RATE from 1000 to 10000
(10× change) on the same b0a-raw-stream N=1 stack: gateway
rate stays flat at 2,000 pts/s and backend rate at 2,001 pts/s.
If any of the candidate bottlenecks were the cause, 10× input
would produce observable throughput delta — it doesn't.

Root cause: PR #182 (`feat(workload): fake-exporter trace-replay
mode`) changed the OTel MeterProvider's PeriodicReader interval
from `time.Second / time.Duration(rate)` to `time.Second` fixed.
With a 1 s interval the SDK pre-aggregates Counter.Add and
Gauge.Record calls per attribute set within each tick, so the
export rate becomes `cardinality × #instruments × (1/interval)`
= 1000 × 2 × 1 = 2000 pts/s, independent of input rate. N=10
is just 10 concurrent producers each correctly emitting 2k.

Bigger implication: the paper §6.2 "raw vs sketch bandwidth"
story is more fragile than it looks. With the current
fake-exporter, the "raw" baselines (B0a / B0b / B1) are already
SDK pre-aggregated at the producer — they are not emitting
per-sample traffic. The bandwidth delta vs sketch baselines
measures payload shape, not "raw samples vs summary per window".

Lays out four paper-story options (A revert interval / B dual-
interval / C bypass SDK aggregation / D reframe §6.2 as bytes-
per-window). Recommends B + D. Does not land a fix; that's a
follow-up PR that needs a paper-framing decision.

Artifacts:
- docs/n10-bottleneck-rca.md — full write-up
- deploy/eval-results/n10-diagnosis/rate-invariance-20260423.csv —
  the two-row evidence

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

* docs: three-axis SDK aggregation framework + sync stale docs

Formalizes the SDK-side decision point as a (W, L, agg_type)
triple: time window × label projection × encoding. All three
axes are independent and correspond one-to-one to what the
controller's planner emits per metric.

- docs/sdk-aggregation-three-axis-design.md (new) — authoritative
  design doc for the framework. Maps to existing Mode 1/2/3
  vocabulary in delta-transmission-design.md. Enumerates the
  outstanding SDK-side aggregators (raw-buffer + 5 delta
  variants) and the SDK runtime hook (hot-reload AttributeFilter)
  needed to exercise the full planner loop.

- docs/paper-outline.md §6 — split §6.2 into four sub-sweeps
  (a/b/c/d) along the three axes, introduce §6.5 planner-quality
  as an independent experiment, update claims table to express
  bandwidth reduction as a three-factor product.

- PROGRESS.md — bump date to 2026-04-23; point at new design
  doc; enumerate outstanding aggregators (~150 LOC raw-buffer +
  ~500 LOC five delta variants) + fake-exporter knobs +
  measure-baseline producer-side columns.

- docs/n10-bottleneck-rca.md — prepend postscript noting the
  Options A–D recommendation section is superseded by the
  three-axis design. Diagnosis content unchanged.

- TODO.md (top-level) — retire "N=10 throughput collapse" as
  P0 blocker (the RCA closed it); replace with the concrete
  implementation punch list for the three-axis framework.

- deploy/TODO.md — add producer-side measurement requirement
  (producer_cpu_cores / _rss_mib / _bytes_out_per_s) to the
  instrumentation P1 list; the three-axis sweeps read these
  from fake-exporter container directly rather than inferring
  from gateway counters.

Includes the n10-bottleneck-rca.md content from the earlier
diag/n10-bottleneck-rca branch (superseded by this PR — close
that one).

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

* docs: correct scope — delta-encoding is a flag on existing aggregators

Reading opentelemetry-go-patch/sdk/metric/aggregation.go shows
that DeltaTransmission is already a field on DDSketch / CountSketch
/ CountMinSketch / HLLSketch aggregators (landed 2026-03-14), so
there's no need to ship five new AggregationDelta<X> types.

Only KLL lacks delta support, and its multi-level sample-buffer
structure doesn't admit a naive byte-diff — separate design
problem, not a §6.2 blocker.

Updates docs/sdk-aggregation-three-axis-design.md, PROGRESS.md,
and TODO.md to reflect this. Real remaining gap: AggregationRawBuffer
(~150 LOC). Proceeding to implement that next.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Apr 23, 2026
* docs: RCA — "N=10 throughput collapse" is not a bottleneck

PR #185 flagged a universal ~2k pts/s floor at N=10 across all
six baselines and attributed it to a coordinated throttle
(OTLP SDK / Docker userland-proxy / kernel socket buffers).

Rate-invariance test disproves the bottleneck reading. Holding
cardinality=1000 and varying EXPORTER_RATE from 1000 to 10000
(10× change) on the same b0a-raw-stream N=1 stack: gateway
rate stays flat at 2,000 pts/s and backend rate at 2,001 pts/s.
If any of the candidate bottlenecks were the cause, 10× input
would produce observable throughput delta — it doesn't.

Root cause: PR #182 (`feat(workload): fake-exporter trace-replay
mode`) changed the OTel MeterProvider's PeriodicReader interval
from `time.Second / time.Duration(rate)` to `time.Second` fixed.
With a 1 s interval the SDK pre-aggregates Counter.Add and
Gauge.Record calls per attribute set within each tick, so the
export rate becomes `cardinality × #instruments × (1/interval)`
= 1000 × 2 × 1 = 2000 pts/s, independent of input rate. N=10
is just 10 concurrent producers each correctly emitting 2k.

Bigger implication: the paper §6.2 "raw vs sketch bandwidth"
story is more fragile than it looks. With the current
fake-exporter, the "raw" baselines (B0a / B0b / B1) are already
SDK pre-aggregated at the producer — they are not emitting
per-sample traffic. The bandwidth delta vs sketch baselines
measures payload shape, not "raw samples vs summary per window".

Lays out four paper-story options (A revert interval / B dual-
interval / C bypass SDK aggregation / D reframe §6.2 as bytes-
per-window). Recommends B + D. Does not land a fix; that's a
follow-up PR that needs a paper-framing decision.

Artifacts:
- docs/n10-bottleneck-rca.md — full write-up
- deploy/eval-results/n10-diagnosis/rate-invariance-20260423.csv —
  the two-row evidence

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

* docs: three-axis SDK aggregation framework + sync stale docs

Formalizes the SDK-side decision point as a (W, L, agg_type)
triple: time window × label projection × encoding. All three
axes are independent and correspond one-to-one to what the
controller's planner emits per metric.

- docs/sdk-aggregation-three-axis-design.md (new) — authoritative
  design doc for the framework. Maps to existing Mode 1/2/3
  vocabulary in delta-transmission-design.md. Enumerates the
  outstanding SDK-side aggregators (raw-buffer + 5 delta
  variants) and the SDK runtime hook (hot-reload AttributeFilter)
  needed to exercise the full planner loop.

- docs/paper-outline.md §6 — split §6.2 into four sub-sweeps
  (a/b/c/d) along the three axes, introduce §6.5 planner-quality
  as an independent experiment, update claims table to express
  bandwidth reduction as a three-factor product.

- PROGRESS.md — bump date to 2026-04-23; point at new design
  doc; enumerate outstanding aggregators (~150 LOC raw-buffer +
  ~500 LOC five delta variants) + fake-exporter knobs +
  measure-baseline producer-side columns.

- docs/n10-bottleneck-rca.md — prepend postscript noting the
  Options A–D recommendation section is superseded by the
  three-axis design. Diagnosis content unchanged.

- TODO.md (top-level) — retire "N=10 throughput collapse" as
  P0 blocker (the RCA closed it); replace with the concrete
  implementation punch list for the three-axis framework.

- deploy/TODO.md — add producer-side measurement requirement
  (producer_cpu_cores / _rss_mib / _bytes_out_per_s) to the
  instrumentation P1 list; the three-axis sweeps read these
  from fake-exporter container directly rather than inferring
  from gateway counters.

Includes the n10-bottleneck-rca.md content from the earlier
diag/n10-bottleneck-rca branch (superseded by this PR — close
that one).

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

* docs: correct scope — delta-encoding is a flag on existing aggregators

Reading opentelemetry-go-patch/sdk/metric/aggregation.go shows
that DeltaTransmission is already a field on DDSketch / CountSketch
/ CountMinSketch / HLLSketch aggregators (landed 2026-03-14), so
there's no need to ship five new AggregationDelta<X> types.

Only KLL lacks delta support, and its multi-level sample-buffer
structure doesn't admit a naive byte-diff — separate design
problem, not a §6.2 blocker.

Updates docs/sdk-aggregation-three-axis-design.md, PROGRESS.md,
and TODO.md to reflect this. Real remaining gap: AggregationRawBuffer
(~150 LOC). Proceeding to implement that next.

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

* feat(sdk/metric): AggregationRawBuffer — raw-sample encoding slot

Adds AggregationRawBuffer to the SDK as the encoding-axis baseline
for the three-axis framework (see docs/sdk-aggregation-three-axis-design.md):
where every Counter.Add / Gauge.Record call becomes its own
NumberDataPoint on the wire instead of being reduced to a Sum or
LastValue per attribute set per tick.

Implementation:
- sdk/metric/aggregation.go — new AggregationRawBuffer{MaxEventsPerSeries}
  public type, wired into the Aggregation interface and err()
  validation alongside the existing sketch aggregators.
- sdk/metric/internal/aggregate/rawbuffer.go — aggregator impl. Per
  attribute.Distinct key, appends (ts, value) to a bounded slice in
  measure(); in collect() emits each buffered sample as its own
  metricdata.DataPoint[N] inside a Gauge[N], then clears the buffer.
  Overflow on a single series is silently dropped with a per-series
  drop counter (exposing that via a side-channel metric is a
  follow-up tracked in PROGRESS.md).
- sdk/metric/internal/aggregate/aggregate.go — Builder.RawBuffer
  method, mirroring the KLLSketch / HLLSketch shape.
- sdk/metric/pipeline.go — dispatch into Builder.RawBuffer +
  isAggregatorCompatible.

Tests: four unit tests covering the core contract (every sample
emitted, second collect is empty i.e. no cumulative semantics
retained, per-series cap honoured, many-attribute-set isolation).

Design notes:
- Both delta and cumulative paths call the same collect(). Raw-buffer
  has no meaningful cumulative semantics — re-emitting all history
  every tick would be useless — so the buffer always resets after
  collect regardless of requested temporality.
- We cap per-series, not globally, because the L (label-projection)
  axis sweep intentionally drives cardinality down; a global cap
  would couple the two axes.

Pre-existing hllsketch build break fixed in passing:
- sketchlib-go renamed HyperLogLog.Insert → InsertValue (float64
  arg) and Estimate → EstimateCardinality. Two-line patch in
  hllsketch.go to restore a green build. The sketchlib-go
  call surface is unchanged at semver zero, so this is
  mechanical drift, not a behaviour change.

Stacked on docs/sdk-three-axis-framework — review that first for
the design context.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Apr 23, 2026
* docs: RCA — "N=10 throughput collapse" is not a bottleneck

PR #185 flagged a universal ~2k pts/s floor at N=10 across all
six baselines and attributed it to a coordinated throttle
(OTLP SDK / Docker userland-proxy / kernel socket buffers).

Rate-invariance test disproves the bottleneck reading. Holding
cardinality=1000 and varying EXPORTER_RATE from 1000 to 10000
(10× change) on the same b0a-raw-stream N=1 stack: gateway
rate stays flat at 2,000 pts/s and backend rate at 2,001 pts/s.
If any of the candidate bottlenecks were the cause, 10× input
would produce observable throughput delta — it doesn't.

Root cause: PR #182 (`feat(workload): fake-exporter trace-replay
mode`) changed the OTel MeterProvider's PeriodicReader interval
from `time.Second / time.Duration(rate)` to `time.Second` fixed.
With a 1 s interval the SDK pre-aggregates Counter.Add and
Gauge.Record calls per attribute set within each tick, so the
export rate becomes `cardinality × #instruments × (1/interval)`
= 1000 × 2 × 1 = 2000 pts/s, independent of input rate. N=10
is just 10 concurrent producers each correctly emitting 2k.

Bigger implication: the paper §6.2 "raw vs sketch bandwidth"
story is more fragile than it looks. With the current
fake-exporter, the "raw" baselines (B0a / B0b / B1) are already
SDK pre-aggregated at the producer — they are not emitting
per-sample traffic. The bandwidth delta vs sketch baselines
measures payload shape, not "raw samples vs summary per window".

Lays out four paper-story options (A revert interval / B dual-
interval / C bypass SDK aggregation / D reframe §6.2 as bytes-
per-window). Recommends B + D. Does not land a fix; that's a
follow-up PR that needs a paper-framing decision.

Artifacts:
- docs/n10-bottleneck-rca.md — full write-up
- deploy/eval-results/n10-diagnosis/rate-invariance-20260423.csv —
  the two-row evidence

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

* docs: three-axis SDK aggregation framework + sync stale docs

Formalizes the SDK-side decision point as a (W, L, agg_type)
triple: time window × label projection × encoding. All three
axes are independent and correspond one-to-one to what the
controller's planner emits per metric.

- docs/sdk-aggregation-three-axis-design.md (new) — authoritative
  design doc for the framework. Maps to existing Mode 1/2/3
  vocabulary in delta-transmission-design.md. Enumerates the
  outstanding SDK-side aggregators (raw-buffer + 5 delta
  variants) and the SDK runtime hook (hot-reload AttributeFilter)
  needed to exercise the full planner loop.

- docs/paper-outline.md §6 — split §6.2 into four sub-sweeps
  (a/b/c/d) along the three axes, introduce §6.5 planner-quality
  as an independent experiment, update claims table to express
  bandwidth reduction as a three-factor product.

- PROGRESS.md — bump date to 2026-04-23; point at new design
  doc; enumerate outstanding aggregators (~150 LOC raw-buffer +
  ~500 LOC five delta variants) + fake-exporter knobs +
  measure-baseline producer-side columns.

- docs/n10-bottleneck-rca.md — prepend postscript noting the
  Options A–D recommendation section is superseded by the
  three-axis design. Diagnosis content unchanged.

- TODO.md (top-level) — retire "N=10 throughput collapse" as
  P0 blocker (the RCA closed it); replace with the concrete
  implementation punch list for the three-axis framework.

- deploy/TODO.md — add producer-side measurement requirement
  (producer_cpu_cores / _rss_mib / _bytes_out_per_s) to the
  instrumentation P1 list; the three-axis sweeps read these
  from fake-exporter container directly rather than inferring
  from gateway counters.

Includes the n10-bottleneck-rca.md content from the earlier
diag/n10-bottleneck-rca branch (superseded by this PR — close
that one).

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

* docs: correct scope — delta-encoding is a flag on existing aggregators

Reading opentelemetry-go-patch/sdk/metric/aggregation.go shows
that DeltaTransmission is already a field on DDSketch / CountSketch
/ CountMinSketch / HLLSketch aggregators (landed 2026-03-14), so
there's no need to ship five new AggregationDelta<X> types.

Only KLL lacks delta support, and its multi-level sample-buffer
structure doesn't admit a naive byte-diff — separate design
problem, not a §6.2 blocker.

Updates docs/sdk-aggregation-three-axis-design.md, PROGRESS.md,
and TODO.md to reflect this. Real remaining gap: AggregationRawBuffer
(~150 LOC). Proceeding to implement that next.

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

* feat(sdk/metric): AggregationRawBuffer — raw-sample encoding slot

Adds AggregationRawBuffer to the SDK as the encoding-axis baseline
for the three-axis framework (see docs/sdk-aggregation-three-axis-design.md):
where every Counter.Add / Gauge.Record call becomes its own
NumberDataPoint on the wire instead of being reduced to a Sum or
LastValue per attribute set per tick.

Implementation:
- sdk/metric/aggregation.go — new AggregationRawBuffer{MaxEventsPerSeries}
  public type, wired into the Aggregation interface and err()
  validation alongside the existing sketch aggregators.
- sdk/metric/internal/aggregate/rawbuffer.go — aggregator impl. Per
  attribute.Distinct key, appends (ts, value) to a bounded slice in
  measure(); in collect() emits each buffered sample as its own
  metricdata.DataPoint[N] inside a Gauge[N], then clears the buffer.
  Overflow on a single series is silently dropped with a per-series
  drop counter (exposing that via a side-channel metric is a
  follow-up tracked in PROGRESS.md).
- sdk/metric/internal/aggregate/aggregate.go — Builder.RawBuffer
  method, mirroring the KLLSketch / HLLSketch shape.
- sdk/metric/pipeline.go — dispatch into Builder.RawBuffer +
  isAggregatorCompatible.

Tests: four unit tests covering the core contract (every sample
emitted, second collect is empty i.e. no cumulative semantics
retained, per-series cap honoured, many-attribute-set isolation).

Design notes:
- Both delta and cumulative paths call the same collect(). Raw-buffer
  has no meaningful cumulative semantics — re-emitting all history
  every tick would be useless — so the buffer always resets after
  collect regardless of requested temporality.
- We cap per-series, not globally, because the L (label-projection)
  axis sweep intentionally drives cardinality down; a global cap
  would couple the two axes.

Pre-existing hllsketch build break fixed in passing:
- sketchlib-go renamed HyperLogLog.Insert → InsertValue (float64
  arg) and Estimate → EstimateCardinality. Two-line patch in
  hllsketch.go to restore a green build. The sketchlib-go
  call surface is unchanged at semver zero, so this is
  mechanical drift, not a behaviour change.

Stacked on docs/sdk-three-axis-framework — review that first for
the design context.

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

* feat(fake-exporter): three-axis SDK knobs + widened label schema

Rewrites the fake-exporter to expose the three-axis knobs defined
in docs/sdk-aggregation-three-axis-design.md. The paper's §6.2
sub-sweeps (time / label / encoding) drive all three via env:

  EXPORTER_SDK_WINDOW        PeriodicReader interval (default 15s).
                             Paper's W axis.
  EXPORTER_SDK_PROJECTION    Comma-separated attribute keys to
                             keep; "" = keep all, "-" = drop all.
                             Paper's L axis, implemented via
                             sdkmetric.View AttributeFilter.
  EXPORTER_SDK_AGG           Aggregator kind. Paper's encoding
                             axis. Supported:
                               default | sum | raw-buffer |
                               dd-full  | dd-delta |
                               kll |
                               cms-full | cms-delta |
                               cs-full  | cs-delta  |
                               hll-full | hll-delta

Paper raw-baseline story now wires the SDK-native AggregationRawBuffer
(landed in feat/aggregation-raw-buffer), so every Counter.Add and
Gauge.Record becomes its own NumberDataPoint on the wire at each
window flush — no more conflating the encoding axis with the
per-tick SDK aggregation behaviour.

Workload changes:
- Drops `EXPORTER_RATE` (was a no-op under SDK aggregation; see
  docs/n10-bottleneck-rca.md). Warns loudly when set so stale
  compose files surface.
- Adds `EXPORTER_FREQ_HZ` (default 10 Hz per series) as the
  app-layer event rate. Orthogonal to SDK_WINDOW.
- Widens the synthetic label schema from 2 dims (zone, pod) to
  4 dims (zone, rack, node, pod). Default max cardinality is now
  4 × 10 × 25 × 10 = 10000; every EXPORTER_*_VALS override is a
  separate env. The L-axis sweep needs at least 3 dims to cover
  {full, 3-of-4, 2-of-4, 1-of-4, 0-of-4}.
- One goroutine per series, each ticking at period = 1/FREQ_HZ.
  Cleaner isolation than the old shared ticker + mu.Lock pattern.

Build:
- go.mod now pins v1.41.0 and replaces every otel sibling at
  ../../opentelemetry-go (the combined upstream + patch tree
  produced by restore_opentelemetry_go_patches.sh). Replaces must
  live here because the patched sdk/metric module's own replaces
  don't apply when this exporter is the main module.
- Dockerfile.fake-exporter now takes sketchlib-go via
  BuildKit --build-context (no submodule hop), copies in the
  opentelemetry-go combined tree, and rewrites the sketchlib-go
  path in go.mod for /src.
- Locally verified: go build clean, image builds clean,
  binary starts and parses all three knobs + deprecation warning.

Follow-up: measure-baseline.py producer-side columns, then
§6.2 sweeps.

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

---------

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