Repository navigation
feat(asap-tools): run sketch-bench workload tables end to end with the paced feeder - #841
Conversation
…l plan without re-solving Reads a plan from sketch-bench's autosketch_vs_asap plan file and the eval table's `e2e` block (PromQL queries, the plan RQEs each reads, metric labels), and writes streaming and inference YAML through the same converter as --planner milp. experiment_run_e2e.py gains controller.prebuilt_output_dir to deploy those configs instead of running the planner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check saved picks against the RAQE's window rules, refuse duplicate picks, reject avg queries before the leaf split, and take a roll-up's grouping from the plan so the shared guard catches it. Share the flag checks of --planner milp and saved. Copy only the two prebuilt configs into a cleared output dir, refusing the dir itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e paced feeder - exporters.exporter_list.paced_feeder: experiment_run_e2e.py runs one paced_feeder process per (sink, metric) on the worker nodes, pushing over remote write to the query engine's ingest (sketchdb mode) and/or Prometheus (other modes, or query_prometheus_too). It starts once its sinks accept writes, shares one --start-ms and seed, and fails the run if a feeder dies. Prometheus does not forward fed metrics to the query engine. - Prometheus (bare-metal and containerized) starts with --web.enable-remote-write-receiver when the feeder is configured. - client_options.align_to_interval: repetition k evaluates at floor((start - query_time_offset) / T) * T + k * T and is sent query_time_offset after that time. - sketch_bench_e2e/sketch_bench_e2e_configs.py writes an experiment_type config from a table's e2e block: one query group per interval, the feeder block, metrics, and controller.prebuilt_output_dir; structured queries only with a SQL file. - setup_dependencies.sh builds the feeder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…aced feeder The feeder sends each tick's requests concurrently, so a series' samples can reach Prometheus' remote-write receiver out of order within a second, and Prometheus rejects them without an out-of-order window. When the paced feeder is configured, the generated prometheus.yml (used by bare-metal and containerized Prometheus) sets storage.tsdb.out_of_order_time_window: 5s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Records metrics (http, flows) reach remote write as one sample per value field, many sharing a series and timestamp, which Prometheus' receiver rejects. check_paced_feeder_config now takes the experiment modes: it accepts records metrics when every mode feeds ASAPQuery only and refuses them when a mode feeds Prometheus (baseline or query_prometheus_too). The config generator feeds a table's records metrics only with --sql-queries (only the structured queries read them); the config then has no baseline mode, and records metrics stay out of `metrics`, which with prebuilt planner configs only keeps fed metrics out of Prometheus' forwarding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
- Pass --samples-per-sec only to gauge and counter feeders and the new
records_per_sec as --records-per-sec only to records feeders; the
feeder exits on the other kind's flag.
- SQL query groups get time_window_seconds: 0; their templates read only
{{ end_time }} / {{ end_datetime }}, but the client refuses to render
templates without a window.
- Take the shared --start-ms after the per-node binary check and table
copy, so the 5 s lead covers only the launches.
- duration_secs must also cover the feeder's startup check before
steady_state_wait and the queries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…d feeder The feeder now keeps each series' samples of a tick in one ordered request, and Prometheus 2.53's remote-write receiver accepted a full feed (48M samples) with no out-of-order window and no rejections. This reverts f7e5a66; Prometheus keeps --web.enable-remote-write-receiver. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Brings in #828 (the base, merged with later review changes), #860 and the rest of main. Planner files, experiment_utils/config.py and test_prebuilt_controller_output.py take main's versions (this branch never changed them). experiment_run_e2e.py takes main's copy_prebuilt_controller_output call. main_prometheus_client.py keeps both align_to_interval and main's simulated_start_time, refusing a group that sets both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Code reviewNote: relies on #840's feeder behavior (per-series ordered requests within a tick).
Design: the workload table is parsed in two places ( Tests: no coverage for findings 1, 2, 3, 5. |
Plan for review findings #2 and #5Real numbers from the sketch-bench tables: queries repeat every T = 60s (most), 300s or 1s, with ranges of 15m / 1h / 6h / 24h. The feeder sends one sample per second per series. The first query goes out ~3 min after the feeder starts. ProblemTake
Every query compares answers over windows shifted by 19 samples, because the feeders started at different seconds within the minute. The difference comes from the input, not the sketches. (#2) With T = 300s, the first query rounds down to the previous 5-minute mark, which falls before the feed started roughly 40% of the time, so it sees no data. (#5) Why not just query at "feeder start + k minutes" (10:03:23, 10:04:23, ...)? ASAPQuery precomputes windows on whole minutes, so those times wouldn't line up with them. Proposed fix: backdate the data to the last 5-minute markThe feeder stamps its first sample with the most recent multiple of the largest T (5 min) instead of the current time. Every timestamp then runs a fixed few minutes behind real time.
Each query is sent once its data has arrived (at real time eval + delay + Risks
|
Each mode's feeder now stamps its data from the last multiple of the aligned query intervals (sending it in real time from a shared send time), and aligned query groups evaluate at that origin + k*T, sent once the data has arrived. Every mode then answers over the same samples, evaluation times stay on ASAPQuery's windows, and the first window is never empty. Also from review: - refuse forward_unsupported_queries when sketchdb mode gives Prometheus no data (the generator sets it false), and auto_discover_labels - take the fed metrics from the feeder block - launch feeders with one SSH per node and fail if that overruns the lead - stop feeders at exit after a failed run - resolve a relative workload_table; check parallelism only for sketchdb Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-runner # Conflicts: # asap-tools/experiments/experiment_utils/services/docker_prometheus.py # asap-tools/experiments/experiment_utils/services/prometheus.py
…844) * feat(experiments): fake_exporter-equivalent dataset for ClickHouse generated2 workloads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(experiments): translate generated2 PromQL workloads to ClickHouse SQL configs Adds a client_options.simulated_start_time so templated SQL can be evaluated against bulk-loaded data with fixed timestamps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(asap-tools): translate sketch-bench e2e PromQL to ClickHouse SQL configs sketch_bench_workloads.py reads a sketch-bench table's workloads[0].e2e block and writes, per table, the metric tables' init SQL, one SQL file per query interval and a baseline experiment_run_clickhouse.py config whose query groups repeat every interval. Queries keep generate_workloads.py's Prometheus 2.53 semantics (range vectors read (T - d, T], extrapolatedRate for rate/increase, quantileExactInclusive) and add the classic template shapes: topk over sum by, sum/count/quantile by over instant vectors (latest sample per series in (T - 1s, T]) and rates, and the ratio of two quantile_over_time. Eval time is {{ end_time }} floored to the interval. Unsupported shapes and structured (non-PromQL) entries raise. generate_workloads.extrapolated_rate now builds on rate_from_samples, which the new module reuses; its SQL output is unchanged. The test compares every generated statement in clickhouse-local against a Python implementation of the Prometheus semantics at several eval times, covering empty windows and every extrapolation branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): use Prometheus 2.53's closed windows in sketch-bench SQL Experiments run Prometheus 2.53.2 (installation/prometheus/install.sh), whose range selectors and instant lookback are closed, [T - d, T]; left-open windows arrived in 3.0. Aligned eval times put a sample on the left boundary, so the SQL now reads ts >= T - d. The Python reference selects [T - d, T] as well. generate_workloads.py's generated2 SQL is unchanged; only the shared rate_from_samples docstring no longer names a window shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): multi-grouping sketch-bench queries in ClickHouse SQL Structured e2e entries {table, value, capability, grouping, quantile, window_secs, interval_secs} become pooled queries over a records table. Cardinality is uniqExact of the value field per group, and quantile is quantileExactInclusive over all of a group's samples. Both read the same closed [T - W, T] window, with T floored to the interval as the PromQL queries are. init.sql now creates records tables, with one Float64 column per value field. Each table's structured queries are also written in ASAPQuery's SQL frontend dialect (COUNT(DISTINCT col), QUANTILE(q, col), BETWEEN DATEADD(s, -W, NOW()) AND NOW()) to asapquery.sql. The clickhouse-local test adds a skewed records dataset in which endpoint is a child of service. It checks template 16's CUBE over (region, service, endpoint) and several cardinality groupings against a Python reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): ASAPQuery's left-open window for multigroup queries Multigroup (structured) queries involve no Prometheus, so they now read ASAPQuery's left-open window (E - W, E] instead of the closed [E - W, E] that classic PromQL keeps to match Prometheus 2.53. ASAPQuery's SQL frontend and precompute windows are (start, end], so ClickHouse and ASAPQuery compute over the same samples. Query gets a `left_open` flag that structured entries set; the raw SQL uses `ts > E - W`. The ASAPQuery-dialect file ends its window at the floored eval time instead of NOW(). The frontend cannot floor a literal, so the client's {{ end_datetime }} is moved back by {{ end_time % T }} inside DATEADD: ts BETWEEN DATEADD(s, -{{ end_time % T + W }}, '{{ end_datetime }}') AND DATEADD(s, -{{ end_time % T }}, '{{ end_datetime }}') which the frontend parses to (start, duration) = (E - W, W). The Python reference for multigroup now pools records in (t - w, t]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): request aligned eval times in sketch-bench ClickHouse configs The SQL floors {{ end_time }} to T, but the current query client sleeps T after each repetition's queries finish, so its eval times drift by the query latency and the arms answer different windows. Each query group now sets `align_to_interval: true` and `query_time_offset: 10` (PR #841's default). With #841's client, repetition k is evaluated at floor((start - O) / T) * T + k * T and sent O seconds later. The current client ignores `align_to_interval` (unknown client options become unused attributes) and shifts its eval time back by the offset. The module docstring records the drift until #841's client lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): compare ts with DateTime64 bounds in sketch-bench raw SQL ClickHouse 26.10 turns `ts > <integer>` on a DateTime64 key column into `ts in [<integer> + 1, +Inf)` for index pruning, so the left-open multigroup SQL dropped the samples in the window's first second from the result (A6: raw vs MV relative differences of 1e-6 to 2.6e-3 on 49 of 52 queries). The raw SQL now compares ts with toDateTime64(<bound>, 3) on both ends, for classic and multigroup queries. A regression test runs a left-open and a closed query in clickhouse-local over a table with 8-row granules and samples every 7 ms around the window start. Without the cast it reproduces the loss (8435 of 8571 rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): take ASAPQuery SQL from the table's structured entries sketch-bench's exporter now writes each structured e2e entry's ASAPQuery SQL (`sql`, e.g. `... WHERE ts BETWEEN DATEADD(s, -300, NOW()) AND NOW() ...`), so it is the single ASAPQuery SQL renderer. asapquery.sql now takes that SQL as written and only replaces NOW() with the floored eval time; this module's own dialect renderer is gone. The ClickHouse raw SQL still comes from the structured fields. The frontend's DATEADD takes only NOW(), a literal or a CAST as its base, so NOW() becomes a datetime literal floored in the client's Jinja template: {{ end_datetime }} (UTC) with its seconds floored for T dividing 60, or its minutes for 60 | T | 3600. Other intervals raise. All 936 distinct structured queries in sketch-bench cd51be9's tables (T = 60 s and 300 s), rendered through the client's QueryTemplate at three end times, parse in ASAPQuery's SQL frontend to (start, duration) = (floor(E) - W, W) and pass its matcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): address #844 review on sketch-bench SQL rendering - Render an empty structured grouping as the grand total, no GROUP BY. - Comment PromQL on one line so multi-line PromQL stays a comment. - Reject PromQL over a records metric, and ASAPQuery sql without NOW(). - Document the 1 s instant lookback and the UTC server requirement. - Drop PR references from comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(asap-tools): validate sketch-bench table inputs at generation time - Raise on an unknown structured table, a label-less metric, more than one workload, and an interval longer than --duration-secs. - Warn when --starting-delay-secs is shorter than offset + longest window. - Strip a trailing ';' from ASAPQuery sql; reuse convert()'s label map. - Correct the comment on which tables the runner fills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Milind Srivastava <milindsrivastava1997@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
milindsrivastava1997
left a comment
There was a problem hiding this comment.
Automated code review: 10 findings (inline).
| (by_interval(promql), {}), | ||
| # The SQL templates read only {{ end_time }} / {{ end_datetime }} (the | ||
| # window is in the SQL); the client requires a window to render them. | ||
| (by_interval(sql), {"time_window_seconds": 0}), |
There was a problem hiding this comment.
SQL query groups get a group-level time_window_seconds: 0, but generate_controller_client_configs only strips client_options, and the planner's QueryGroup is #[serde(deny_unknown_fields)]. A run with --sql-queries and no --prebuilt-output-dir fails because asap-planner rejects the config with 'unknown field time_window_seconds'.
|
|
||
| local_config_dir = os.path.join(local_experiment_dir, "paced_feeder_config") | ||
| os.makedirs(local_config_dir, exist_ok=True) | ||
| with open(os.path.join(local_config_dir, "cmds.sh"), "w") as f: |
There was a problem hiding this comment.
PACED_FEEDER_START_LEAD_SECS (5 s) has to cover SSH launches that run one node at a time, and run_cmd_with_retry sleeps 5 s on each rc=255 retry. With roughly 8 or more worker nodes, or a single retried SSH, the launches pass send_at_ms and the run aborts. Launch each node with Popen and wait on all of them, or scale the lead with the node count.
| # One shared send time for every process of this mode, taken after the | ||
| # SSH prep above so the lead covers only the launches. | ||
| send_at_ms = (math.ceil(time.time()) + PACED_FEEDER_START_LEAD_SECS) * 1000 | ||
| start_ms = backdated_start_ms(send_at_ms, align_secs) |
There was a problem hiding this comment.
start_ms is backdated to a multiple of the LCM of the aligned intervals, so the lag can approach the full LCM. Prometheus rejects samples older than its newest sample minus 1 h, and its newest samples come from wall-clock scrapes. With intervals of 420 s and 3600 s, the LCM is 25200 s and every fed sample is rejected, so the baseline comes back empty. Consider capping or rejecting an LCM above Prometheus's out-of-order window.
| """LCM, in seconds, of the repeat intervals of the client config's | ||
| `align_to_interval` groups; 1 if there are none.""" | ||
| intervals = [ | ||
| g["repetition_delay_ms"] // 1000 |
There was a problem hiding this comment.
repetition_delay_ms // 1000 turns a sub-second interval into 0, so the LCM is 0 and backdated_start_ms divides by zero (repetition_delay_ms: 500 raises ZeroDivisionError). The client's 'whole-second repetition_delay_ms' error is never reached. A value like 1500 ms is silently truncated to 1 s. Validate that the value is a whole number of seconds here.
| ] | ||
| # With only fed metrics there is nothing for Prometheus to forward. (An | ||
| # empty list without the feeder keeps meaning "forward everything".) | ||
| forward_to_query_engine = bool(metrics_to_remote_write) or not fed_metrics |
There was a problem hiding this comment.
When a paced feeder is configured and the filtered remote-write list is empty, all forwarding to ASAPQuery is now disabled. Before this change, an empty list meant 'forward everything'. With a paced feeder plus another scraped exporter whose metrics aren't listed in experiment_params.metrics, ASAPQuery never receives that exporter's data.
| ) | ||
| paced_feeder_service = PacedFeederService(provider, args) | ||
| # Otherwise a failed run leaves feeders pushing into the next run's sinks. | ||
| atexit.register(paced_feeder_service.stop) |
There was a problem hiding this comment.
atexit.register(paced_feeder_service.stop) runs on every exit, even when flow.no_teardown is set. Prometheus and ASAPQuery are left running for inspection, but the feeders are killed, so data stops arriving.
| ) | ||
| logger.debug("Current unix time: {}", int(current_time.timestamp())) | ||
| lag_secs = float(data_lag_secs) | ||
| offset_secs = getattr(query_group, "query_time_offset", 0) |
There was a problem hiding this comment.
Aligned groups default query_time_offset to 0. Each repetition then fires at t_k + L, before ASAPQuery's 5 s allowed lateness closes the window, so sketchdb returns partial results. Consider requiring an offset for aligned groups, or defaulting it to at least the allowed lateness.
| # No $(...): the SSH command is double-quoted on this side. | ||
| result = self.provider.execute_command( | ||
| node_idx=node_idx, | ||
| cmd=f"pgrep -fc '{PACED_FEEDER_PROCESS_PATTERN}' | grep -qx {expected}", |
There was a problem hiding this comment.
pgrep -fc counts every paced_feeder process on the node and requires the count to equal exactly this run's job count. A stray or concurrent feeder on a shared node (count 3, expected 2) fails a healthy run. Checking the PIDs of the launched processes (e.g. echo $! per launch) would be more robust.
| elif config.get("samples_per_sec") is not None: | ||
| cmd += f" --samples-per-sec {config['samples_per_sec']}" | ||
| if config.get("extra_flags"): | ||
| cmd += f" {config['extra_flags']}" |
There was a problem hiding this comment.
extra_flags and other config values are inserted unescaped into a command that get_ssh_cmd wraps in double quotes. A value like --label "a b", or one containing $, breaks the quoting, and the only symptom is the vague 'expected N processes' error later. Consider shlex.quote.
|
|
||
| import constants | ||
| from .base import BaseService | ||
| from .fake_exporters import get_exporter_target_nodes |
There was a problem hiding this comment.
Nit: get_exporter_target_nodes now has a generic name but still lives in fake_exporters.py, so the paced feeder depends on the fake-exporter module. Consider moving it to a shared module.
…eries (#845) * feat(experiments): fake_exporter-equivalent dataset for ClickHouse generated2 workloads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(experiments): translate generated2 PromQL workloads to ClickHouse SQL configs Adds a client_options.simulated_start_time so templated SQL can be evaluated against bulk-loaded data with fixed timestamps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(asap-tools): translate sketch-bench e2e PromQL to ClickHouse SQL configs sketch_bench_workloads.py reads a sketch-bench table's workloads[0].e2e block and writes, per table, the metric tables' init SQL, one SQL file per query interval and a baseline experiment_run_clickhouse.py config whose query groups repeat every interval. Queries keep generate_workloads.py's Prometheus 2.53 semantics (range vectors read (T - d, T], extrapolatedRate for rate/increase, quantileExactInclusive) and add the classic template shapes: topk over sum by, sum/count/quantile by over instant vectors (latest sample per series in (T - 1s, T]) and rates, and the ratio of two quantile_over_time. Eval time is {{ end_time }} floored to the interval. Unsupported shapes and structured (non-PromQL) entries raise. generate_workloads.extrapolated_rate now builds on rate_from_samples, which the new module reuses; its SQL output is unchanged. The test compares every generated statement in clickhouse-local against a Python implementation of the Prometheus semantics at several eval times, covering empty windows and every extrapolation branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): use Prometheus 2.53's closed windows in sketch-bench SQL Experiments run Prometheus 2.53.2 (installation/prometheus/install.sh), whose range selectors and instant lookback are closed, [T - d, T]; left-open windows arrived in 3.0. Aligned eval times put a sample on the left boundary, so the SQL now reads ts >= T - d. The Python reference selects [T - d, T] as well. generate_workloads.py's generated2 SQL is unchanged; only the shared rate_from_samples docstring no longer names a window shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): multi-grouping sketch-bench queries in ClickHouse SQL Structured e2e entries {table, value, capability, grouping, quantile, window_secs, interval_secs} become pooled queries over a records table. Cardinality is uniqExact of the value field per group, and quantile is quantileExactInclusive over all of a group's samples. Both read the same closed [T - W, T] window, with T floored to the interval as the PromQL queries are. init.sql now creates records tables, with one Float64 column per value field. Each table's structured queries are also written in ASAPQuery's SQL frontend dialect (COUNT(DISTINCT col), QUANTILE(q, col), BETWEEN DATEADD(s, -W, NOW()) AND NOW()) to asapquery.sql. The clickhouse-local test adds a skewed records dataset in which endpoint is a child of service. It checks template 16's CUBE over (region, service, endpoint) and several cardinality groupings against a Python reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): baseline_mv arm for sketch-bench e2e queries The sketch-bench ClickHouse configs now run baseline_mv beside baseline: exact mergeable states in materialized views, no sketches. There is one AggregatingMergeTree view per (metric, value, statistic, interval T). Its key is the T-second bucket plus the union of the queries' groupings and their child_of parents (every label for per-series statistics). The states are sumState, countState, quantilesExactInclusiveState over the queries' levels, argMaxState per series for instant vectors, and per-series first/last sample, min/max ts and count for rate/increase. Those feed the same extrapolatedRate SQL. Bucket b holds (b - T, b], and an `edge` flag keeps samples at exactly b apart. A closed window [E - W, E] therefore merges the buckets in (E - W, E] plus the edge rows of bucket E - W. A query whose window is not a multiple of T raises, so instant vectors are only served at T = 1 s. The init SQL creates the views after their tables and before any data arrives. The test runs every MV query next to its raw SQL in clickhouse-local. Results match exactly, except sums, which agree within 1e-9 relative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): ASAPQuery's left-open window for multigroup queries Multigroup (structured) queries involve no Prometheus, so they now read ASAPQuery's left-open window (E - W, E] instead of the closed [E - W, E] that classic PromQL keeps to match Prometheus 2.53. ASAPQuery's SQL frontend and precompute windows are (start, end], so ClickHouse and ASAPQuery compute over the same samples. Query gets a `left_open` flag that structured entries set; the raw SQL uses `ts > E - W`. The ASAPQuery-dialect file ends its window at the floored eval time instead of NOW(). The frontend cannot floor a literal, so the client's {{ end_datetime }} is moved back by {{ end_time % T }} inside DATEADD: ts BETWEEN DATEADD(s, -{{ end_time % T + W }}, '{{ end_datetime }}') AND DATEADD(s, -{{ end_time % T }}, '{{ end_datetime }}') which the frontend parses to (start, duration) = (E - W, W). The Python reference for multigroup now pools records in (t - w, t]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): per-arm init SQL so baseline ingest skips the views Each sketch-bench table now gets one config per arm. The baseline config (init.sql) creates only the tables. The baseline_mv config (init_mv.sql) creates the tables and their views. Both files drop the views first, so none left by an earlier baseline_mv run keeps feeding on the recreated tables. Both configs list both arms' SQL files, so saved results label every query by its baseline text. experiment_run_clickhouse.py monitored ingest under the baseline mode's client config and output dir. A baseline_mv-only run has neither, so ingest now uses baseline when the run has it, and otherwise the run's first mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): left-open bucket selection for multigroup views A multigroup query's window is now ASAPQuery's (E - W, E], so its baseline_mv SQL merges just the buckets in (E - W, E] and skips the edge rows that hold a closed window's left boundary. Classic PromQL queries keep the closed window and its edge rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): request aligned eval times in sketch-bench ClickHouse configs The SQL floors {{ end_time }} to T, but the current query client sleeps T after each repetition's queries finish, so its eval times drift by the query latency and the arms answer different windows. Each query group now sets `align_to_interval: true` and `query_time_offset: 10` (PR #841's default). With #841's client, repetition k is evaluated at floor((start - O) / T) * T + k * T and sent O seconds later. The current client ignores `align_to_interval` (unknown client options become unused attributes) and shifts its eval time back by the offset. The module docstring records the drift until #841's client lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): skip the baseline_mv arm when its views can't serve a query convert() planned views for every query, and plan_views raises when a window is not a multiple of the interval (e.g. an instant vector, which reads [T - 1s, T], at T > 1 s). So such a table failed to convert even for the baseline arm, which needs no views. Now a table with such a query gets only the baseline arm: init.sql, the raw SQL and the baseline config, plus a note on stderr naming the queries. Dropping just those queries from the MV arm would shift its query indices against the baseline SQL that labels both arms' results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): compare ts with DateTime64 bounds in sketch-bench raw SQL ClickHouse 26.10 turns `ts > <integer>` on a DateTime64 key column into `ts in [<integer> + 1, +Inf)` for index pruning, so the left-open multigroup SQL dropped the samples in the window's first second from the result (A6: raw vs MV relative differences of 1e-6 to 2.6e-3 on 49 of 52 queries). The raw SQL now compares ts with toDateTime64(<bound>, 3) on both ends, for classic and multigroup queries. A regression test runs a left-open and a closed query in clickhouse-local over a table with 8-row granules and samples every 7 ms around the window start. Without the cast it reproduces the loss (8435 of 8571 rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): cast view bucket bounds; regression test covers the MV SQL The views' `bucket` column is a DateTime (whole seconds), so integer bounds already filter it exactly. They are now toDateTime(<bound>) anyway, to match the column type, as the raw SQL's ts bounds now are. The key-pruning regression test, merged from feat/e2e-sql-from-table, now passes the views to load_script (this branch's signature). It also runs each query's MV SQL over views with 1-row granules and checks raw and MV against the expected counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * feat(asap-tools): take ASAPQuery SQL from the table's structured entries sketch-bench's exporter now writes each structured e2e entry's ASAPQuery SQL (`sql`, e.g. `... WHERE ts BETWEEN DATEADD(s, -300, NOW()) AND NOW() ...`), so it is the single ASAPQuery SQL renderer. asapquery.sql now takes that SQL as written and only replaces NOW() with the floored eval time; this module's own dialect renderer is gone. The ClickHouse raw SQL still comes from the structured fields. The frontend's DATEADD takes only NOW(), a literal or a CAST as its base, so NOW() becomes a datetime literal floored in the client's Jinja template: {{ end_datetime }} (UTC) with its seconds floored for T dividing 60, or its minutes for 60 | T | 3600. Other intervals raise. All 936 distinct structured queries in sketch-bench cd51be9's tables (T = 60 s and 300 s), rendered through the client's QueryTemplate at three end times, parse in ASAPQuery's SQL frontend to (start, duration) = (floor(E) - W, W) and pass its matcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae * fix(asap-tools): address #845 review on sketch-bench materialized views - Keep every view in one database that each init drops, so views another workload left on the metric tables stop firing on inserts. - Clear the table's generated files before writing, so a skipped baseline_mv arm or a removed interval leaves no stale SQL or config. - Render views with no label keys (empty grouping) as valid DDL. - Raise ValueError when child_of names an unknown label. - Share one mv_servable() check between convert and plan_views. - Docstring: E for the eval time, T for the interval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(asap-tools): render sketch-bench outputs before clearing old ones A re-run that failed validation or rendering had already deleted the earlier SQL files, leaving its config pointing at missing files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Milind Srivastava <milindsrivastava1997@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Feeders now start at the next multiple of the aligned intervals' LCM (at most 600 s) rather than stamping data in the past, so the client sends each query at its eval time + query_time_offset and the feeder loses --send-at-ms. Also from review: - refuse sub-second aligned intervals and query_time_offset below ASAPQuery's 5 s allowed lateness - give feeder launches 10 s per node on top of the 5 s lead - keep feeders running under flow.no_teardown - refuse --sql-queries without --prebuilt-output-dir - refuse other exporters next to the feeder with no listed metrics Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up (06289b2)Backdating is gone. It needed too many guards (Prometheus' sample-age limit, client lag plumbing). Instead, each mode's feeders now wait for the next multiple of the aligned intervals' LCM and start there. For today's tables (T = 1, 60, 300 s) that's a wait of at most 5 min per mode. Every mode's data still starts on the same boundary, so each query covers the same samples in every mode. The client sends each query at its eval time + Addressed:
Not changed: the |
Uses #828's
--planner savedandcontroller.prebuilt_output_dir(merged); now based onmain. This is #824 B4, revised: the data comes from the paced feeder instead of fake_exporter.Summary
Runs a sketch-bench #777 workload table end to end with
experiment_run_e2e.py. The paced feeder pushes the table's data in real time, and queries repeat at each RQE's interval T at evaluation times aligned to T.--samples-per-secfor gauges/counters,--records-per-secfor records),time_window_secondson SQL groups, the shared start time taken after setup, and a minimum feed duration that includes the feeder's startup check.experiment_utils/services/paced_feeder.py), configured byexporters.exporter_list.paced_feeder: {workload_table, metrics, seed, duration_secs, samples_per_sec?, extra_flags?}:--start-ms, so every system receives identical data.sketchdbmode pushes to ASAPQuery's ingest, and to Prometheus too withquery_prometheus_too; other modes push to Prometheus. Insketchdbmode Prometheus no longer forwards the fed metrics to ASAPQuery, so they don't arrive twice.streaming.parallelism == 1, andduration_secsoutlasts the queries.setup_dependencies.shbuilds the feeder.--web.enable-remote-write-receiver(bare metal and container). No out-of-order window is needed: the feeder keeps each series' samples of a tick in one ordered request (fix(asap-tools): send paced feeder ticks concurrently, ordered per series, in (t, t+1s] #840), and a CloudLab run (48M samples, 200,000 rows/s) had 0 rejections without one.client_options.align_to_intervalevaluates repetition k atfloor((start − O)/T)·T + k·Tand sends it O =query_time_offsetseconds later, so all systems answer the same windows.sketch_bench_e2e/sketch_bench_e2e_configs.py): from a workload table (+ optional prebuilt controller output dir), it writes an experiment_type YAML. Query groups come frome2e.queriesgrouped byinterval_secs(the same grouping as--planner saved), with metrics frome2e.metricsand the feeder block.Not covered yet
records) metrics (feeder support in feat(asap-tools): generate records metrics in the paced feeder #843) are fed to ASAPQuery only, as<table>.<field>for its SQL frontend. The startup check refuses them when any mode also sends to Prometheus (baseline, orquery_prometheus_too). The generator feeds them only with--sql-queries, and then writes asketchdb-only config; the Prometheus baseline for the classic queries of analltable is a separate run.--sql-queries FILE.Testing
tests/test_paced_feeder.py(feeder command lines, sinks per mode, startup checks, the Prometheus receiver flag),tests/test_sketch_bench_e2e_configs.py(config generation from a real classic table snippet) andqueriers/prometheus-client/tests/test_query_time.py(alignment math). The experiments suite passes (89 tests) under pytest and unittest. A CI step now runs the prometheus-client tests. black/flake8 are clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae