test(perf): add monotonic quantile regression controls

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
discord9
2026-09-10 14:53:08 +08:00
parent 83da7217f1
commit e0656b4cf1
4 changed files with 191 additions and 3 deletions
@@ -45,7 +45,7 @@ pub(super) struct PromRemoteWriteArgs {
value_pattern: ValuePattern,
#[arg(long, default_value_t = 0.0)]
value_base: f64,
#[arg(long, default_value_t = 0.125)]
#[arg(long, default_value_t = 0.125, allow_negative_numbers = true)]
value_step: f64,
#[arg(long, default_value_t = 97)]
value_cardinality: u64,
@@ -211,8 +211,16 @@ fn splitmix64(mut value: u64) -> u64 {
mod tests {
use std::collections::HashSet;
use clap::Parser;
use super::*;
#[derive(Parser)]
struct Cli {
#[command(flatten)]
prom_remote_write: PromRemoteWriteArgs,
}
fn args(pattern: ValuePattern) -> PromRemoteWriteArgs {
PromRemoteWriteArgs {
endpoint: "http://127.0.0.1:4000/v1/prometheus/write".to_string(),
@@ -239,6 +247,20 @@ mod tests {
}
}
#[test]
fn value_step_accepts_a_separate_negative_argument() {
let cli = Cli::try_parse_from([
"query_perf_fixture",
"--metric",
"fixture_metric",
"--value-step",
"-1",
])
.expect("negative value step should parse");
assert_eq!(cli.prom_remote_write.value_step, -1.0);
}
#[test]
fn seeded_random_is_deterministic_in_range_and_highly_distinct() {
let mut args = args(ValuePattern::SeededRandom);
@@ -0,0 +1,83 @@
# PromQL quantile-over-time ascending-value negative control.
#
# Values are strictly increasing by global sample ordinal. This uses the
# Prometheus remote-write/metric-engine ingestion and storage path, whereas
# promql_quantile_selection uses direct readable SSTs. Both use 128 series,
# 780 samples per series, 15s cadence, and the same evaluation queries.
[case]
name = "promql_quantile_ascending"
description = "PromQL quantile_over_time q=0.9 ascending-value remote-write negative control"
[scenario]
kind = "prom_remote_write_then_query"
[scenario.remote_write]
database = "public"
metric = "promql_quantile_ascending"
series_count = 128
samples_per_series = 780
sample_chunk_size = 780
flush_every_sample_chunks = 1
start_unix_millis = 1704067200000
step_millis = 15000
chunk_series_count = 128
[scenario.remote_write.value]
pattern = "unique"
base = 0
step = 1
[scenario.remote_write.prom_store]
pending_rows_flush_interval = "1s"
max_batch_rows = 100000
# Selector and response-path control; it does not cover quantile behavior.
[[scenario.queries]]
name = "plain_selector_control_2h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') promql_quantile_ascending{host=~'host.*'}"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_1m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_ascending{host=~'host.*'}[1m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_ascending{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_1h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_ascending{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
# host0000 is series ordinal 0. The 1h range at 1704070800 excludes the left
# bound, spanning ordinals 1..240; q=0.9 rank 0.9 * (240 - 1) = 215.1 yields 216.1.
[[scenario.queries]]
name = "quantile_over_time_q90_1h_host0000_eval"
kind = "tql"
query = "TQL EVAL (1704070800, 1704070800, '15s') quantile_over_time(0.9, promql_quantile_ascending{host='host0000'}[1h])"
warmup = 0
iterations = 1
@@ -0,0 +1,83 @@
# PromQL quantile-over-time descending-value negative control.
#
# Values are strictly decreasing by global sample ordinal. This uses the
# Prometheus remote-write/metric-engine ingestion and storage path, whereas
# promql_quantile_selection uses direct readable SSTs. Both use 128 series,
# 780 samples per series, 15s cadence, and the same evaluation queries.
[case]
name = "promql_quantile_descending"
description = "PromQL quantile_over_time q=0.9 descending-value remote-write negative control"
[scenario]
kind = "prom_remote_write_then_query"
[scenario.remote_write]
database = "public"
metric = "promql_quantile_descending"
series_count = 128
samples_per_series = 780
sample_chunk_size = 780
flush_every_sample_chunks = 1
start_unix_millis = 1704067200000
step_millis = 15000
chunk_series_count = 128
[scenario.remote_write.value]
pattern = "unique"
base = 0
step = -1
[scenario.remote_write.prom_store]
pending_rows_flush_interval = "1s"
max_batch_rows = 100000
# Selector and response-path control; it does not cover quantile behavior.
[[scenario.queries]]
name = "plain_selector_control_2h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') promql_quantile_descending{host=~'host.*'}"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_1m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_descending{host=~'host.*'}[1m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_descending{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
[[scenario.queries]]
name = "quantile_over_time_q90_1h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') quantile_over_time(0.9, promql_quantile_descending{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 10
# host0000 is series ordinal 0. At 1704070800, the 1h range is ordinals 1..240;
# sorted values are -240..-1, so rank 215.1 interpolates -25 to -24 as -24.9.
[[scenario.queries]]
name = "quantile_over_time_q90_1h_host0000_eval"
kind = "tql"
query = "TQL EVAL (1704070800, 1704070800, '15s') quantile_over_time(0.9, promql_quantile_descending{host='host0000'}[1h])"
warmup = 0
iterations = 1
@@ -1,7 +1,7 @@
# PromQL quantile-over-time latency regression coverage.
#
# This is wave-only end-to-end coverage. Ascending- and descending-value
# microbenchmark controls remain separately required.
# This is wave-only end-to-end coverage. The strictly monotonic remote-write
# controls are promql_quantile_ascending and promql_quantile_descending.
[case]
name = "promql_quantile_selection"