Skip to content

feat(asap-tools): generate records metrics in the paced feeder - #843

Merged
milindsrivastava1997 merged 15 commits into
mainfrom
feat/paced-feeder-records
Oct 14, 2026
Merged

milindsrivastava1997 merged 15 commits into
mainfrom
feat/paced-feeder-records

Conversation

@zzylol

@zzylol zzylol commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Stacked on the workload-table feeder PR (base branch feat/paced-feeder-facts).

Summary

The feeder now generates sketch-bench's multi-grouping data: type: "records" metrics (http, flows, from sketch-bench#209's e2e.metrics). Each record carries labels and several value fields. Record n depends only on the seed, the metric name and n.

  • Labels: value index i has weight (i+1)^-skew, a child is drawn given its parent, and child values name their parent. This mirrors sketch-bench's label_shares.
  • Keys (user_id, src_ip): Zipf rank over keys, drawn by binary search over a precomputed CDF with a guide table.
  • Burst (flows): with probability share, the record's dst_subnet is set to the burst value and its key is drawn uniformly from a separate range of keys burst keys.
  • Latency: 1000 · Pareto(a) (scale from configs/datagen/hydra_http_latency.yaml), times the service's factor 10^u (aqpbm-datagen's scale_range [1, 10]), times 10 for the anomaly endpoint "0.0".
  • Time: a second's records are spread over 100 slots of 10 ms, at t + (s+1)·10 ms, and each tick is sent concurrently (fix(asap-tools): send paced feeder ticks concurrently, ordered per series, in (t, t+1s] #840).
  • Remote write: each record is one sample per value field, named <metric>.<field> (e.g. http.latency), which is how ASAPQuery's SQL-mode router matches table.value_column. Prometheus is not a target for records metrics.
  • ClickHouse: one table per metric: ts, the labels, then the value fields (Float64). See the README.
  • CLI: --records-per-sec scales records metrics down. --verify-facts also checks each label's top shares and the burst share over 1e6 generated records, within 5 standard errors.
  • One feeder can send a whole all table (classic and records metrics together).

Testing

  • Facts check on multigroup-shared1: every label subset matches (http 15, up to 10,000 combinations; flows 7, up to 3e6). The shares match label_shares (e.g. service 0: 0.29841 expected, 0.29847 generated). The burst gets 5.03% of the records.
  • CloudLab, ClickHouse 26.10, full rate, 10 s:
    • http: 20M rows, 1,000 distinct timestamps (first at t+10 ms), every endpoint under its service, all 10,000 label combinations.
    • The anomaly endpoint's median latency is 9.97× that of the other endpoints of its service. Service medians differ (scaling).
    • flows: 40M rows, dst_subnet=999 share 0.0500, exactly 10,000 burst keys.
  • CloudLab, ASAPQuery precompute_engine (default lateness), http at 20k records/s via remote write: 0 late drops, and all 200,000 http.latency samples were routed to the aggregation.
  • Throughput limits are the sinks', not the feeder's:
    • Against a local stub, ticks are 171 ms (http) and 195 ms (flows) for remote write, and about 110 ms for ClickHouse.
    • On one node, a single ClickHouse taking http and flows together (6e6 rows/s) and a single engine taking http's 4e6 samples/s both fell behind (ticks about 1.1 s). End-to-end runs should scale with --records-per-sec or spread the sinks.
  • 35 unit tests: shares, child conditionals, burst share and keys, anomaly and scaling, determinism across sinks, remote-write names and labels, RowBinary columns, facts check. fmt/clippy clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae

zzylol and others added 3 commits October 13, 2026 01:39
…n (t, t+1s]

Remote write sent one sub-second slot at a time, so at most one request was
in flight per slot. At 200 samples/s per series ASAPQuery's ingest took about
45 ms per 1e4-row request and the feeder reached only 0.2-0.27M rows/s.

Each tick now goes out concurrently. Its samples sit in (t, t+1s], the same
left-open interval as ASAPQuery's one-second windows, so the reordering within
a tick never crosses a window boundary and stays within the allowed lateness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
`--workload-table FILE` reads `workloads[0].e2e.metrics` of a sketch-bench
workload table and sends its gauge and counter metrics (or only `--metric`):
labels with `child_of` fan out under their parent, the others form a cross
product; series get Zipf scales from a seeded rank shuffle; gauges are scaled
Pareto draws and counters grow by their scale per sample. `--verify-facts`
checks, for every label subset, the distinct label-value tuples of the
generated series against the description. The `--groups` workload sends the
same bytes as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
`--workload-table` now also sends a table's `records` metrics (the
multi-grouping `http` and `flows`). Each record is a stateless hash of the
seed, the metric and its index: labels are Zipf(`skew`) draws (a child given
its parent, value `i` of rank `i`), keys are Zipf ranks, a burst sends
`share` of the records to its label value from its own keys, and Pareto
values are scaled by their `scaled_by` label's factor (aqpbm-datagen's
log-uniform [1, 10]) and by the anomaly factor on the anomaly label's first
value. A tick starting at t spreads its records over 10 ms slots in
(t, t + 1 s], slot s at t + (s + 1) * 10 ms, and is sent concurrently like
gauge/counter ticks, so one feeder can send both kinds. Records are
generated inside the encoding tasks. Remote write sends one sample per value
field, named `<metric>.<field>`; ClickHouse gets one row per record in a
table named after the metric. `--records-per-sec` scales a run down, and
`--verify-facts` also compares the shares of each label's heaviest values
and of the burst with sketch-bench's `label_shares()`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
zzylol and others added 6 commits October 13, 2026 02:02
…aply

A Zipf keeps a dense CDF and a u32 guide entry per value, so a records
label's cardinality and a field's keys are capped at 1e8 (1.2 GB), with an
error naming the cap; burst keys are drawn uniformly and keep only the 2^53
bound. `--verify-facts` no longer enumerates every label combination of a
records metric: per label subset it prints the possible tuples and how many
the sampled 1e6 records hold, failing only if they hold more. The share
checks are unchanged. The `Workload` doc now says one feeder sends both
kinds of metric.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…equest

Concurrent requests split a tick by time slot, so a series' later sample
could arrive first and ASAPQuery's increase accumulator, which reads samples
in arrival order, took the lower counter value for a reset. A tick's rows
are now series-major and requests are cut only between series (the batch
size rounded down to whole series, at least one), so each series' samples of
a tick arrive in one request in timestamp order; requests stay concurrent.
Prometheus therefore needs no out-of-order window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Table-mode gauge and counter metrics are now series-major too: each metric's
tick is cut into requests only between series, so a series' samples of a
tick arrive in one request, in timestamp order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Gauge and counter requests hold whole series, as on feat/paced-feeder-facts;
records requests are still cut anywhere, since no records query reads a
series in arrival order and Prometheus is not a target for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
@milindsrivastava1997

Copy link
Copy Markdown
Contributor

Code review

  1. Medium — records.rs ~426 (timestamp_ms): many samples per series share a timestamp. Records land in 10 ms slots, so records with the same label tuple become samples of one series at one timestamp (~400 per timestamp for the heaviest http tuple at 2M/s). Fine for ASAPQuery's engine today (no per-series timestamp dedupe), but Prometheus/VictoriaMetrics or any timestamp-keyed consumer silently keeps one value. "Never send records to Prometheus" is only a comment (main.rs ~1229) — consider a runtime guard or doc warning.
  2. Medium — records.rs ~578-595: label_values can OOM before its size check. Child labels build every "parent.i" string before ensure!(len <= u32::MAX). Parent and child each at 1e5 pass the per-label cap and then try 1e10 strings. Check the cardinality product first.
  3. Low — main.rs ~322 (verify_facts): label coverage relaxed to actual <= expected, so missing/collapsed tuples still pass. When expected is far below the 1e6 sample size, equality could be required.
  4. Low — records.rs ~374: a burst on a child label overrides only the child index, so it spreads across all parents. Documented as intended and the shipped burst is on a root label; worth confirming it matches sketch-bench semantics.
  5. Low (cost) — records.rs 450-483: label_coverage and share_checks each regenerate the 1e6 records, and coverage allocates a Vec<u32> per record per subset (~15M allocs for http). Generate once; pack tuples into an integer key.
  6. Low — main.rs ~1229: records batches are cut anywhere and sent concurrently, so a series' samples can arrive out of order within a tick. OK for ASAPQuery, same caveat as (1).

Nits: Rows::{Samples, Records} mismatch panics at runtime (encode.rs ~23); verify_facts returns Result<bool> but never Err; workload.rs module doc says "either … or" though mixed workloads are supported.

milindsrivastava1997 and others added 4 commits October 13, 2026 23:02
A tick straddling a 1s boundary can close a window before its other
series' samples arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at-ms

Feeders launched at different moments can share one aligned event-time
origin: data is stamped from --start-ms but sent from --send-at-ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	asap-tools/data-sources/paced-feeder/src/encode.rs
#	asap-tools/data-sources/paced-feeder/src/main.rs
#	asap-tools/data-sources/paced-feeder/src/workload.rs
# Conflicts:
#	asap-tools/data-sources/paced-feeder/src/main.rs
…ords

# Conflicts:
#	asap-tools/data-sources/paced-feeder/README.md
#	asap-tools/data-sources/paced-feeder/src/encode.rs
#	asap-tools/data-sources/paced-feeder/src/main.rs
#	asap-tools/data-sources/paced-feeder/src/table.rs
#	asap-tools/data-sources/paced-feeder/src/workload.rs
@milindsrivastava1997
milindsrivastava1997 changed the base branch from feat/paced-feeder-facts to main October 14, 2026 17:17
@milindsrivastava1997
milindsrivastava1997 merged commit 587382c into main Oct 14, 2026
11 of 12 checks passed
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.

2 participants