Skip to content

fix: an unwrapped value Loki converts makes its sample and one that overflows fails the query like Loki - #730

Merged
szibis merged 8 commits into
mainfrom
fix/unwrap-gate-parsefloat
Oct 23, 2026
Merged

szibis merged 8 commits into
mainfrom
fix/unwrap-gate-parsefloat

Conversation

@szibis

@szibis szibis commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Gap semantics/unwrap-gate-parsefloat-divergences. The unwrap gate stood in for strconv.ParseFloat, time.ParseDuration and humanize ParseBytes (Loki pkg/logql/log/metrics_extraction.go:316-338) with regular expressions. Those capped the exponent and did not know hexadecimal floats, exponent digit separators, infinity, comma runs or Unicode space folding. Values Loki converts were dropped, and values that overflow were kept where Loki fails the query.

VictoriaLogs facts, checked in its v1.43.1 and v1.50.0 source and by running its query code in-process:

  • math reads hex floats, 1e308 and 1e1_0 as Go does.
  • math gives NaN for infinity, nan and every overflow.
  • Durations saturate at int64 nanoseconds.
  • sum restarts at a NaN.
Value form Now How
hex floats, 1e308, 1e1_0, any exponent same as Loki the pattern is Go's float grammar; VictoriaLogs math already reads them
infinity same as Loki rewritten to inf on a copy by a conditional replace_regexp
overflow (1e309, 1000e307, 0x1p2000, 2562048h, 19EB) query fails with Loki's 400 text a range filter after math drops the row; the counting gate flags it, and the proxy re-reads it to answer Loki's error
bytes ,5, 1.5,0, no-break and other Unicode spaces, Kelvin sign same as Loki the pattern is ParseBytes's own syntax
nan NaN for sum/avg/stddev/stdvar/rate, dropped for max/min, as Loki VictoriaLogs groups nan rows apart; the proxy applies NaN to the answer, because VictoriaLogs' sum cannot carry it

Also fixed:

  • Leading-zero durations (01m) were silently kept as NaN.
  • Sliding unwrap windows dropped inf rows.
  • The raw evaluators' min/max now treat NaN like Loki.
  • +nan and -nan are reported.

Remaining (the registry row stays open with this shorter list):

  • Durations within about 1 µs of the int64 limit.
  • nan under max/min windows.
  • Mixed chains over nan.
  • Shapes the counting gate cannot rewrite (sort/limit after stats, two gates).

Tests

  • Pattern tests against Go's own functions on 600k generated float strings and 400k byte strings.
  • TestUnwrapCountedGateFlagsOverflow, TestUnwrapNaNReachesTheLokiAnswer and TestUnwrapInfinityAndInstantNaNReachTheAnswer.
  • Overflow values added to TestUnwrapConversionErrorDifferential.
  • e2e vs Loki: TestCompat_UnwrapValueFormsParity, TestCompat_UnwrapOverflowParity.

Pending before ready (proofs that need the compose stack)

  • Live e2e on VictoriaLogs v1.49.0, v1.52.0 and v1.53.0 (commands below). The gate sends constructs the old one did not: x:<=inf, -(a b) inside format if, filter A or (B), math x default 0, exponent literals.
  • Visual fix proof, the stack A/B (cost per row on VictoriaLogs) and the backend-safety load test (commands below). The committed bench/ab/results/2026-10-12-unwrapgate-*.json predate the current base.

Rebased onto main (2026-10-22, third update)

Base 4e4f3e8a (v2.30.0 plus #841, #842 and #843), head 7e4575e8: eight commits, all signed. The seven original subjects are kept in order ("chore(conformance): regenerate the parity reports for the unwrap gate rows" holds the regenerated files), plus one new last commit, "test: leave the registered ungrouped nan sum out of the value forms parity run" (see below). Previous head 0584a2db (base 304b5549). Main gained, among others, #727 (sum/avg/max/min_over_time unwrap sample at end), #728 (first/last_over_time unwrap under an aggregation) and the release metadata commits for v2.26.1 to v2.30.0; this PR no longer overlaps any open PR.

Conflict Resolution
CHANGELOG.md Rebuilt from origin/main on every rebase: #842's ### Security entry stays first under ## [Unreleased] and this PR's ### Fixed entry follows it, above ## [2.30.0]; no released section is edited (git diff origin/main HEAD --numstat -- CHANGELOG.md is 55 0). The entry is added in the first commit, as before.
internal/proxy/unwrap_ungrouped_sum_differential_test.go (the emulator's pipe switch: main added math copy and unpack_json branches, this PR the filter <field>:* branch) Kept all three branches.
internal/proxy/extraction_list_outside_without_test.go (the golden table, see below) Took main's table, regenerated at every commit that changes the sent text.
bench/ab/shapes.json (both appended a set: main json-error-split, this PR unwrap-gate) Took main's file and re-added the unwrap-gate set unchanged; checked by JSON-diffing every set of the result against main (21 sets equal) and unwrap-gate against the PR (equal); 22 sets, ranges unchanged. File stays in the repo's indent=1 format.
conformance/registry/generated/{wiring,perf-evidence}.json, conformance/reports/{parity-gaps,performance}.md Took main's and regenerated with wire.py, gaps.py, roadmap.py, parity_gaps.py and perf_evidence.py; all five pass --check on the final tree (gaps.md and roadmap.md are unchanged by this PR).

bench/visual/spec.json is not touched by this PR.

CI finding fixed in this update

TestCompat_UnwrapValueFormsParity compared sum (sum_over_time(...)) and sum (avg_over_time(...)) over the nan forms (an ungrouped instant sum). That is a registered residual of this PR (semantics/unwrap-gate-parsefloat-divergences: Loki answers NaN, the proxy drops the series), so the e2e group core failed on 8 subtests. The test now skips that outer for those two functions, like the other residuals it already skips. The residual itself is unchanged and stays registered.

Golden table: what changed and why it is correct

The table is extractionListGoldens in internal/proxy/extraction_list_outside_without_test.go (69 entries: mode instant, tumbling or sliding x query). Each entry pins the LogsQL sent to VictoriaLogs and the answer the proxy gives for a shape that goes through an extraction list (| logfmt amount, route). #727 changed 24 answers (the end sample) and this PR changes the text sent. There is no update flag: the test prints got and want on a mismatch. The table was regenerated by taking main's table and rewriting each mismatching entry from the got the test printed, with a script that refuses to report success if an entry's Answer differs from the one on main.

  • Answers: 0 of 69 changed. They are main's, so they carry fix: sum, avg, max and min over time with unwrap answer the sample at end on query_range, like Loki #727's extra sample at start (the old values one step later, the last one on end, semantics/unwrap-range-last-point) and fix: first_over_time and last_over_time with unwrap under an aggregation read each series' sample from VictoriaLogs row_min and row_max instead of a stats function it lacks #728's first/last handling. The fixture holds plain decimal amount values only (no hexadecimal floats, inf/infinity, nan, separators or overflow), and on those Loki's strconv.ParseFloat converts exactly the rows the old gate kept. The gate change therefore cannot move an answer, and the unchanged answers are the check that it does not.

  • Sent text: 50 entries changed, 19 did not. The 19 are the shapes without an unwrap gate (count/rate/bytes over an extraction list) and the instant raw max_over_time, which sends limit 1000001. The 50 are the unwrap shapes (sum, avg, min, max, count, topk, bottomk over max/min/avg_over_time, grouped and ungrouped, without, in the three modes) and two sum(rate(... | unwrap ...)) shapes. What changed in the text:

    • the value pattern is Go's ParseFloat grammar (hexadecimal floats, 1e308, exponent digit separators) instead of the capped regular expression;
    • the flag states __lvp_bad 0/1/2 (and 3 for infinity in the counting gate), so nan rows are grouped apart and the proxy can answer NaN like Loki;
    • infinity is shortened to inf on a copy (copy, conditional replace_regexp) because VictoriaLogs' math reads inf but not infinity;
    • a range flag -(__lvp_v:<=inf) after math marks a value that overflowed (Loki fails such a query, so the proxy must see it) and math __lvp_v default 0 keeps the column numeric;
    • behind a rate's math, the presence filter filter __lvp_rate:* the translator adds because of the copy.

    Loki v3.7.7 (pkg/logql/log/metrics_extraction.go) converts with strconv.ParseFloat and fails the query on a conversion error, which is the behaviour all of these pipes implement; none of them changes what is returned for a decimal value.

Gates on the final tree (Go sources identical to the tested tree apart from #842's one-line change and the e2e test skip) (no Docker, GOMEMLIMIT=3GiB GOMAXPROCS=4; Go 1.27.3)

  • gofmt -s -l: clean. go build ./..., go vet ./..., go vet -tags e2e ./test/e2e-compat/: clean.
  • golangci-lint run (v2.14.0, the version in ci.yaml, built with Go 1.27.3): 0 issues.
  • go test -short -p 1 -count=1 ./...: all 13 packages ok. Peak RSS with /usr/bin/time -l (measured on the previous base 88edc57b; the commits since change no Go code): head 1,824,292,864 B (1.70 GiB), 133 s; origin/main at the same base 1,778,909,184 B (1.66 GiB), 118 s (+2.5 percent).
  • -race -short on the tests this PR adds or changes (TestUnwrap*, TestSeriesLimit_*, TestExtractionListOutsideWithout*, TestStripUnwrapCounter, internal/proxy and internal/translator): 425 passed.
  • go test ./internal/... -count=1 -json then python3 scripts/ci/check_conformance.py --go-results: conformance gate ok (478 tests declare 140 items, 136 with a passing run, 0 failing; no fail event in the run).
  • The five generators (wire, gaps, roadmap, parity_gaps, perf_evidence) with --check: pass.
  • The unit tests listed in ci.yaml (python3 -m unittest scripts.ci.tests.test_check_quality_gate ... test_scan_release_image): 203 tests OK. scripts/ci/sync_observability_assets.sh --check: in sync.
  • python3 scripts/ci/check_changelog_pr.py --base origin/main --head HEAD: ok.

Translator cost (re-measured on the new base). go test -bench 'BenchmarkTranslate_Unwrap' -benchmem, origin/main vs head, benchstat, n=12 and n=14. The machine was heavily loaded by other jobs while this ran (a main-versus-main control drifted by 80 percent between two runs), so the times are only indicative; the allocation figures are exact.

Interleaved run (main and head test binaries alternated 14 times, so a load drift hits both):

Benchmark sec/op main sec/op head B/op main B/op head allocs main allocs head
Translate_UnwrapSumByJSON 61.49 us +-10% 66.35 us +-11% (~, p=0.51) 40.65 KiB 42.21 KiB (+3.8%) 211 213 (+2)
Translate_UnwrapMaxPlain 12.87 us +-7% 14.01 us +-12% (+8.9%, p=0.004) 2.773 KiB 4.316 KiB (+55.7%) 35 37 (+2)

Sequential -count=6 x 2 runs (quieter moment): UnwrapSumByJSON 27.28 us -> 26.28 us (~, p=0.20); UnwrapMaxPlain 5.46 us -> 6.66 us (+22%, p<0.001), with the same B/op and allocs/op. So the gate costs +2 allocations and about 1.5 KiB per unwrap translation, and between about 9 and 22 percent of the translation time of the smallest query (about 1.2 us on an idle machine, against 5.5 us); the larger query is within noise. The VictoriaLogs-side cost per row is the A/B below. The earlier text of this PR said the absolute cost stays under a microsecond; on this base the small query is a little above it.

go.mod. github.com/dustin/go-humanize v1.0.1 is added (test-only: the property tests compare the bytes pattern and its port parseHumanBytes with humanize.ParseBytes); main only mentions it in comments. go mod tidy -diff is clean.

Proofs to be refreshed (phase 2, need the stack)

Supported VictoriaLogs for the e2e runs: the floor v1.49.0 and the newest v1.53.0, with the CI pin v1.52.0 (the PR text from before the floor moved named older lines). Commands run from the worktree $SCRATCH/wt-730 on the PR head; the Colima VM colima-lvp is the Docker host (DOCKER_CONTEXT=colima-lvp), and the shared e2e stack must not be running while a fresh stack is started on the same ports.

1. e2e vs Loki (one fresh stack per group and per VictoriaLogs version)

docker --context colima-lvp build -t loki-vl-proxy:e2e-pr730 .
for V in v1.49.0 v1.52.0 v1.53.0; do
  ( cd test/e2e-compat && DOCKER_CONTEXT=colima-lvp PROXY_IMAGE=loki-vl-proxy:e2e-pr730 \
      VICTORIALOGS_IMAGE=victoriametrics/victoria-logs:$V docker compose up -d --no-build \
    && ../../scripts/ci/wait_e2e_stack.sh 180 )
  # this PR's tests (they ingest their own fixtures and wait for Loki; no TestSetup needed)
  go test -v -tags=e2e -timeout=600s -count=1 -run '^(TestCompat_UnwrapValueFormsParity|TestCompat_UnwrapOverflowParity)$' ./test/e2e-compat/
  ( cd test/e2e-compat && DOCKER_CONTEXT=colima-lvp docker compose down -v )
done

Then the two CI groups that cover the touched code, each on its own fresh stack on v1.52.0 and v1.49.0 (same up/down lines, -run pattern copied from .github/workflows/ci.yaml):

go test -v -tags=e2e -timeout=600s -count=1 -run '^(TestSetup_IngestLogs|TestCompat_.*|TestExtended_.*|TestChaining_.*|TestAlertingCompat_.*|TestExplore_HTTPQueryRangeContracts|TestLokiFunctions_.*|TestGrafanaDatasourceCatalogAndHealth|TestProxyCompatibilitySurface|TestPinnedCompatibilityMatrixMatchesCompose)$' ./test/e2e-compat/
go test -v -tags=e2e -timeout=600s -count=1 -run '^(TestSetup_IngestLogs|TestQuerySemanticsMatrixManifest|TestQuerySemanticsOperationsInventory|TestQuerySemanticsMatrix|TestGrafanaClickout_.*|TestMissingOps_.*|TestRangeMetricCompatibility.*|TestLogQL_Exhaustive_.*|TestPipeline_.*|TestEdge_DropErrorInstantQueryReturnsAggregatedResult|TestEdge_DetectedFieldsAfterJsonDropPipelineIncludesJsonFields)$' ./test/e2e-compat/

2. A/B proof (previous release / base vs PR vs Loki, own compose project and ports)

python3 bench/ab/pr_smoke.py --base origin/main --out $SCRATCH/ab-730 --loki-mem 3g --vl-mem 4g --project ab730 --ranges 1h,6h,24h --runs 5 --refresh-s 60 --refresh-runs 5
# sets the selection names for this change: unwrap-gate (G1 to G12, in the PR), control, drilldown-breakdown-exact, extracted-pushdown, extracted-suffix, json-filter-pushdown, loki-profile-otel-metadata, metadata-inventory, metric-latency-gap, parenthesized-range-or-filters, pattern-line-filter-anchoring, plain-name-after-parser, unwrap-conversion-error, unwrap-duration-seconds, unwrap-validity
# a 7d pass and the control set explicitly (perf_matrix.py --set <set> --ranges 1h,6h,24h,7d --runs 5 --target main=... --target branch=... --target loki=... --out raw.json;
#   python3 bench/ab/report.py summarize raw.json --baseline main --candidate branch --label pr730-<set> --save bench/ab/results/)
python3 bench/ab/pr_smoke.py --same-build --out $SCRATCH/ab-730-aa --project ab730a    # noise floor

Plus go test -run '^$' -bench <touched benchmarks> -benchmem -count=6 ./internal/translator ./internal/proxy on origin/main and the head, compared with benchstat, and the backend-safety load test (many 24h/7d queries concurrently, VictoriaLogs RestartCount before and after, /labels probe answering 200).

3. Visual proof (isolated stack, base | PR | Loki)

python3 bench/visual/plan.py --base origin/main --out $SCRATCH/vs730/plan.json
python3 bench/visual/ci_run.py --base origin/main --plan $SCRATCH/vs730/plan.json --out $SCRATCH/vs730 --project vs730 --port-offset 200 --hours 24
python3 bench/visual/visual_comment.py --out $SCRATCH/vs730 --mode artifact --md $SCRATCH/vs730/comment.md

Plan: no fix-proof case is registered (fix_cases is empty), so add a fixes visual entry for the gate if it is visual-coverable; the plan selects the core set and the unwrap entries (explore-unwrap-sum, explore-unwrap-unit-string, explore-unwrap-missing-label, explore-unwrap-conversion-error, explore-unwrap-duration-seconds) at 1h, 31 captures.

4. Real Grafana range matrix: the shapes below through the Loki-type datasources of the proxy and of Loki direct, over 5m, 15m, 1h, 6h, 24h and 7d where the data exists, compared for status, errors, series counts and totals.

Matrix shapes: unwrap over latency_ms, amount, duration_ms, duration(ttl), bytes(size_bytes) with and without | __error__="", rate, max_over_time, instant and range, plus value forms with hexadecimal floats, 1e308, infinity, nan, and an overflowing value (1e309, 19EB) which must answer Loki's 400 text.

The committed bench/ab/results/2026-10-12-unwrapgate-*.json are measured on a base before the v1.49.0 floor and against v2.11.2: replace them with the new run (--save bench/ab/results/), re-run conformance/scripts/perf_evidence.py and gaps.py, and refresh the cost numbers in the registry row semantics/unwrap-gate-parsefloat-divergences and in the changelog entry.

@github-actions github-actions Bot added size/XL Extra large change scope/translator LogQL translator scope/proxy Proxy core scope/docs Documentation scope/tests Tests bugfix Bug fix labels Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

⚪ Performance A/B: no change beyond noise

130 shape×range — 130 ⚪ same · results vs Loki: 104 same, 0 new difference(s), 26 pre-existing

Every shape is within noise of the base build (±30% and 100 ms).

130 unchanged shape×range (within noise)
shape range base p50 PR p50 change Loki p50 PR ÷ Loki result vs Loki
⚪ by service_name 1h 19 ms 22 ms ⚪ +16% 43 ms 0.51× ✅ same
⚪ bytes_over_time by app 1h 40 ms 43 ms ⚪ +7% 71 ms 0.61× ✅ same
⚪ detected_level volume 1h 46 ms 45 ms ⚪ -2% 42 ms 1.07× ✅ same
⚪ drilldown detected_level 1h 37 ms 38 ms ⚪ +3% 33 ms 1.15× ✅ same
⚪ drilldown level volume 1h 7 ms 7 ms ⚪ +0% 43 ms 0.16× ✅ same
⚪ drilldown service landing 1h 19 ms 16 ms ⚪ -16% 34 ms 0.47× ✅ same
⚪ instant json filtered instant 6 ms 5 ms ⚪ -17% 59 ms 0.08× ✅ same
⚪ instant json volume instant 16 ms 16 ms ⚪ +0% 74 ms 0.22× ✅ same
⚪ instant ungrouped rate instant 4 ms 4 ms ⚪ +0% 59 ms 0.07× ✅ same
⚪ json rate by level (no filter, no drop) 1h 400 400 ⚪ +0% 400 — ✅ same
⚪ json volume no filters 1h 103 ms 100 ms ⚪ -3% 60 ms 1.67× ✅ same
⚪ logfmt count by level, filter, drop 1h 243 ms 235 ms ⚪ -3% 52 ms 4.52× ✅ same
⚪ logfmt volume 1h 110 ms 117 ms ⚪ +6% 55 ms 2.13× ✅ same
⚪ logs plain 1h 42 ms 41 ms ⚪ -2% 42 ms 0.98× ✅ same
⚪ logs | json 1h 55 ms 58 ms ⚪ +5% 60 ms 0.97× ✅ same
⚪ logs | logfmt 1h 86 ms 82 ms ⚪ -5% 48 ms 1.71× ✅ same
⚪ plain volume 1h 10 ms 9 ms ⚪ -10% 42 ms 0.21× ✅ same
⚪ stream-label count by app 1h 13 ms 13 ms ⚪ +0% 44 ms 0.30× ✅ same
⚪ topk rate by app 1h 22 ms 26 ms ⚪ +18% 69 ms 0.38× ➖ differs (as base)
⚪ ungrouped rate 1h 6 ms 6 ms ⚪ +0% 82 ms 0.07× ✅ same
⚪ DB3 json field breakdown user_id 1h 26 ms 33 ms ⚪ +27% 35 ms 0.94× ✅ same
⚪ DB4 json field breakdown batch_id 1h 53 ms 53 ms ⚪ +0% 52 ms 1.02× ✅ same
⚪ X1 json filter level_extracted, by level 1h 210 ms 205 ms ⚪ -2% 46 ms 4.46× ✅ same
⚪ X2 json group by level_extracted 1h 164 ms 159 ms ⚪ -3% 45 ms 3.53× ✅ same
⚪ X3 json rate, level_extracted equality 1h 146 ms 146 ms ⚪ +0% 41 ms 3.56× ✅ same
⚪ X4 json bytes, level_extracted regexp 1h 212 ms 209 ms ⚪ -1% 74 ms 2.82× ✅ same
⚪ X5 logfmt filter level_extracted, by level 1h 361 ms 364 ms ⚪ +1% 52 ms 7.00× ✅ same
⚪ E1 logs, json parser over stream labels 1h 79 ms 77 ms ⚪ -3% 43 ms 1.79× ✅ same
⚪ E2 logs, logfmt parser over stream labels 1h 65 ms 66 ms ⚪ +2% 33 ms 2.00× ✅ same
⚪ E3 logs, json, existence filter on level_extracted 1h 86 ms 93 ms ⚪ +8% 47 ms 1.98× ✅ same
⚪ E4 logs, logfmt, existence filter on level_extracted 1h 74 ms 71 ms ⚪ -4% 45 ms 1.58× ✅ same
⚪ E5 logs, json, drop level_extracted 1h 81 ms 74 ms ⚪ -9% 38 ms 1.95× ✅ same
⚪ E6 logfmt count by level_extracted 1h 24 ms 24 ms ⚪ +0% 6 ms 4.00× ✅ same
⚪ E7 json count by level_extracted 1h 85 ms 76 ms ⚪ -11% 16 ms 4.75× ✅ same
⚪ E8 instant logfmt by level_extracted instant 5 ms 5 ms ⚪ +0% 7 ms 0.71× ✅ same
⚪ J1 json no-error count by service_name, one service 1h 59 ms 58 ms ⚪ -2% 28 ms 2.07× ✅ same
⚪ J2 json no-error count by app, namespace 1h 154 ms 155 ms ⚪ +1% 81 ms 1.91× ✅ same
⚪ J3 json no-error count by parsed method 1h 77 ms 78 ms ⚪ +1% 22 ms 3.55× ✅ same
⚪ J4 json no-error count by service_name, all services 1h 173 ms 170 ms ⚪ -2% 106 ms 1.60× ✅ same
⚪ J5 json no-error bytes_rate by app 1h 147 ms 143 ms ⚪ -3% 64 ms 2.23× ✅ same
⚪ A explore volume, 2 filters, drop 1h 80 ms 73 ms ⚪ -9% 58 ms 1.26× ✅ same
⚪ B grouped sum, filter, no drop 1h 57 ms 55 ms ⚪ -4% 42 ms 1.31× ✅ same
⚪ C ungrouped sum, filter, no drop 1h 54 ms 56 ms ⚪ +4% 42 ms 1.33× ✅ same
⚪ D volume, no filters 1h 109 ms 118 ms ⚪ +8% 48 ms 2.46× ✅ same
⚪ E field breakdown pipeline (explore) 1h 54 ms 56 ms ⚪ +4% 41 ms 1.37× ✅ same
⚪ F field breakdown service_version (explore) 1h 78 ms 70 ms ⚪ -10% 46 ms 1.52× ✅ same
⚪ G field breakdown pipeline (drilldown) 1h 25 ms 26 ms ⚪ +4% 33 ms 0.79× ✅ same
⚪ H field breakdown service_version (drilldown) 1h 38 ms 37 ms ⚪ -3% 34 ms 1.09× ✅ same
⚪ I grafana volume of B 1h 55 ms 53 ms ⚪ -4% 39 ms 1.36× ✅ same
⚪ O2 explore log volume (Grafana form), underscore structured-metadata filter 1h 39 ms 39 ms ⚪ +0% 43 ms 0.91× ✅ same
⚪ drilldown detected fields 1h 1 ms 1 ms ⚪ +0% 14 ms 0.07× ➖ differs (as base)
⚪ drilldown detected labels 1h 1 ms 1 ms ⚪ +0% 2 ms 0.50× ➖ differs (as base)
⚪ drilldown landing volume 1h 31 ms 31 ms ⚪ +0% 55 ms 0.56× ➖ differs (as base)
⚪ explore label values env 1h 1 ms 1 ms ⚪ +0% 32 ms 0.03× ✅ same
⚪ explore label values service_name 1h 1 ms 1 ms ⚪ +0% 36 ms 0.03× ✅ same
⚪ explore labels (label browser) 1h 1 ms 1 ms ⚪ +0% 15 ms 0.07× ✅ same
⚪ explore labels with selector 1h 1 ms 1 ms ⚪ +0% 38 ms 0.03× ✅ same
⚪ refresh label values env 1h 16 ms 14 ms ⚪ -12% 33 ms 0.42× ✅ same
⚪ refresh label values service_name 1h 7 ms 7 ms ⚪ +0% 39 ms 0.18× ✅ same
⚪ refresh labels (label browser) 1h 11 ms 10 ms ⚪ -9% 15 ms 0.67× ✅ same
⚪ refresh labels with selector 1h 11 ms 12 ms ⚪ +9% 38 ms 0.32× ✅ same
⚪ DL1 drilldown field breakdown json-expr, level filter 1h 283 ms 260 ms ⚪ -8% 33 ms 7.88× ✅ same
⚪ DL2 drilldown field breakdown, level + stream-label filter before json 1h 23 ms 23 ms ⚪ +0% 7 ms 3.29× ✅ same
⚪ DL3 drilldown logfmt field breakdown, level filter 1h 350 ms 345 ms ⚪ -1% 73 ms 4.73× ➖ differs (as base)
⚪ DL4 instant count, level filter, json+logfmt, 1h instant 274 ms 273 ms ⚪ -0% 780 ms 0.35× ✅ sum ±0.28%
⚪ DL6 explore volume, level filter before json 1h 77 ms 71 ms ⚪ -8% 45 ms 1.58× ✅ same
⚪ PR1 count by level, parenthesised json 1h 100 ms 98 ms ⚪ -2% 47 ms 2.09× ✅ same
⚪ PR2 rate, parenthesised line filter 1h 29 ms 27 ms ⚪ -7% 61 ms 0.44× ✅ same
⚪ PR3 bytes by app, parenthesised regexp 1h 31 ms 30 ms ⚪ -3% 62 ms 0.48× ✅ same
⚪ PR4 count by level, parenthesised parser, offset 1h 106 ms 105 ms ⚪ -1% 50 ms 2.10× ✅ same
⚪ PR5 rate by app, pipeline after the range 1h 112 ms 114 ms ⚪ +2% 82 ms 1.39× ✅ same
⚪ PR6 doubly parenthesised range 1h 27 ms 28 ms ⚪ +4% 61 ms 0.46× ✅ same
⚪ PR7 instant, parenthesised line filter instant 5 ms 5 ms ⚪ +0% 56 ms 0.09× ✅ same
⚪ A1 nginx include GET, log query 1h 15 ms 14 ms ⚪ -7% 9 ms 1.56× ✅ same
⚪ A2 nginx exclude GET, log query 1h 15 ms 15 ms ⚪ +0% 9 ms 1.67× ✅ same
⚪ A3 nginx include GET, count_over_time 1h 10 ms 12 ms ⚪ +20% 6 ms 2.00× ✅ same
⚪ A4 nginx exclude GET, count_over_time 1h 13 ms 12 ms ⚪ -8% 5 ms 2.40× ✅ same
⚪ A5 drilldown exclude, volume by detected_level 1h 17 ms 17 ms ⚪ +0% 5 ms 3.40× ✅ same
⚪ A6 nginx include ends on a literal, log query 1h 11 ms 11 ms ⚪ +0% 4 ms 2.75× ✅ same
⚪ A7 nginx exclude ends on a literal, log query 1h 24 ms 19 ms ⚪ -21% 10 ms 1.90× ✅ same
⚪ A8 nginx include two literals, count_over_time 1h 14 ms 15 ms ⚪ +7% 5 ms 3.00× ✅ same
⚪ A9 nginx include two literals, ends on a literal, count_over_time 1h 14 ms 15 ms ⚪ +7% 5 ms 3.00× ✅ same
⚪ P1 logs, json, level equals 1h 77 ms 76 ms ⚪ -1% 60 ms 1.27× ✅ same
⚪ P2 logs, logfmt, level not equal 1h 18 ms 18 ms ⚪ +0% 5 ms 3.60× ✅ same
⚪ P3 logs, json, level existence 1h 72 ms 70 ms ⚪ -3% 60 ms 1.17× ✅ same
⚪ P4 logfmt count by level 1h 17 ms 17 ms ⚪ +0% 7 ms 2.43× ✅ same
⚪ P5 json count by level 1h 44 ms 46 ms ⚪ +5% 13 ms 3.54× ✅ same
⚪ P6 json count, level filter 1h 9 ms 9 ms ⚪ +0% 13 ms 0.69× ✅ same
⚪ P7 json rate by level, level regexp 1h 13 ms 10 ms ⚪ -23% 13 ms 0.77× ✅ same
⚪ P8 json count by a name that is no stream label 1h 47 ms 45 ms ⚪ -4% 12 ms 3.75× ✅ same
⚪ P9 instant logfmt by level instant 5 ms 4 ms ⚪ -20% 7 ms 0.57× ✅ same
⚪ E1 json duration_ms sum by service 1h 47 ms 47 ms ⚪ +0% 18 ms 2.61× ➖ differs (as base)
⚪ E2 json duration_ms p95 by service 1h 156 ms 154 ms ⚪ -1% 14 ms 11× ✅ same
⚪ E3 logfmt latency_ms rate, production 1h 268 ms 275 ms ⚪ +3% 90 ms 3.06× ➖ differs (as base)
⚪ E4 logfmt size_bytes max by service 1h 26 ms 27 ms ⚪ +4% 10 ms 2.70× ➖ differs (as base)
⚪ E5 json duration_ms sum, instant instant 20 ms 20 ms ⚪ +0% 26 ms 0.77× ✅ same
⚪ E6 logfmt ttl unit string (Loki 400) 1h 400 400 ⚪ +3% 400 — ✅ same
⚪ E7 json hexadecimal trace_id (Loki 400) 1h 400 400 ⚪ +3% 400 — ✅ same
⚪ E8 json duration_ms sum by service, namespace prod 1h 100 ms 103 ms ⚪ +3% 73 ms 1.41× ➖ differs (as base)
⚪ E9 logfmt latency_ms sum by service, namespace prod 1h 167 ms 167 ms ⚪ +0% 69 ms 2.42× ➖ differs (as base)
⚪ D1 logfmt ttl duration_seconds rate by service 1h 29 ms 39 ms ⚪ +34% 8 ms 4.88× ✅ same
⚪ D2 logfmt ttl duration rate by service 1h 27 ms 37 ms ⚪ +37% 7 ms 5.29× ✅ same
⚪ D3 logfmt ttl duration_seconds rate, instant instant 5 ms 11 ms ⚪ +120% 6 ms 1.83× ✅ same
⚪ D4 logfmt ttl duration rate, instant instant 7 ms 12 ms ⚪ +71% 7 ms 1.71× ✅ same
⚪ G1 logfmt amount sum_over_time, no filter 1h 22 ms 22 ms ⚪ +0% 13 ms 1.69× ➖ differs (as base)
⚪ G10 logfmt latency_ms sum_over_time, production 1h 260 ms 267 ms ⚪ +3% 131 ms 2.04× ➖ differs (as base)
⚪ G11 logfmt amount sum_over_time, instant instant 5 ms 5 ms ⚪ +0% 10 ms 0.50× ✅ same
⚪ G12 json duration_ms sum_over_time, instant instant 21 ms 19 ms ⚪ -10% 21 ms 0.90× ✅ same
⚪ G2 json duration_ms sum_over_time, no filter 1h 48 ms 45 ms ⚪ -6% 26 ms 1.73× ➖ differs (as base)
⚪ G3 logfmt amount sum_over_time, error filter 1h 22 ms 22 ms ⚪ +0% 11 ms 2.00× ➖ differs (as base)
⚪ G4 json duration_ms sum_over_time, error filter 1h 46 ms 48 ms ⚪ +4% 22 ms 2.18× ➖ differs (as base)
⚪ G5 logfmt amount sum by rate 1h 24 ms 25 ms ⚪ +4% 10 ms 2.50× ✅ same
⚪ G6 json duration_ms sum by rate 1h 50 ms 51 ms ⚪ +2% 22 ms 2.32× ✅ same
⚪ G7 duration() of a unit string 1h 26 ms 35 ms ⚪ +35% 9 ms 3.89× ➖ differs (as base)
⚪ G8 bytes() of a number 1h 30 ms 36 ms ⚪ +20% 7 ms 5.14× ➖ differs (as base)
⚪ G9 json duration_ms max_over_time 1h 51 ms 51 ms ⚪ +0% 100 ms 0.51× ➖ differs (as base)
⚪ U1 json duration_ms sum by service 1h 44 ms 46 ms ⚪ +5% 17 ms 2.71× ➖ differs (as base)
⚪ U10 instant sum_over_time unwrap instant 18 ms 19 ms ⚪ +6% 19 ms 1.00× ✅ same
⚪ U11 instant max_over_time missing label instant 4 ms 4 ms ⚪ +0% 13 ms 0.31× ✅ same
⚪ U12 json duration_ms over every service 1h 122 ms 122 ms ⚪ +0% 43 ms 2.84× ➖ differs (as base)
⚪ U13 logfmt latency_ms over every service 1h 261 ms 276 ms ⚪ +6% 55 ms 5.02× ➖ differs (as base)
⚪ U14 duration() of a unit string 1h 24 ms 32 ms ⚪ +33% 6 ms 5.33× ➖ differs (as base)
⚪ U2 json duration_ms max by service 1h 49 ms 52 ms ⚪ +6% 32 ms 1.62× ➖ differs (as base)
⚪ U3 json duration_ms sliding sum 1h 45 ms 46 ms ⚪ +2% 26 ms 1.77× ➖ differs (as base)
⚪ U4 logfmt size_bytes sum by service 1h 22 ms 26 ms ⚪ +18% 7 ms 3.71× ➖ differs (as base)
⚪ U5 logfmt ttl unit string (no sample) 1h 23 ms 25 ms ⚪ +9% 5 ms 5.00× ✅ same
⚪ U6 missing label, no parser 1h 15 ms 13 ms ⚪ -13% 11 ms 1.18× ✅ same
⚪ U7 missing label, ungrouped sum 1h 12 ms 11 ms ⚪ -8% 11 ms 1.00× ✅ same
⚪ U8 rate of an unwrapped label 1h 46 ms 49 ms ⚪ +7% 13 ms 3.77× ✅ same
⚪ U9 bytes() conversion 1h 30 ms 32 ms ⚪ +7% 8 ms 4.00× ➖ differs (as base)
Cold (first-run) timings — what a user opening a panel sees
shape range base cold PR cold Loki cold PR ÷ Loki cold
by service_name 1h 18 ms 19 ms 507 ms 0.04×
bytes_over_time by app 1h 41 ms 36 ms 540 ms 0.07×
detected_level volume 1h 59 ms 58 ms 517 ms 0.11×
drilldown detected_level 1h 37 ms 36 ms 473 ms 0.08×
drilldown level volume 1h 8 ms 7 ms 488 ms 0.01×
drilldown service landing 1h 17 ms 16 ms 499 ms 0.03×
instant json filtered instant 6 ms 5 ms 60 ms 0.08×
instant json volume instant 17 ms 16 ms 72 ms 0.22×
instant ungrouped rate instant 4 ms 3 ms 79 ms 0.04×
json rate by level (no filter, no drop) 1h 400 400 400 —
json volume no filters 1h 102 ms 113 ms 593 ms ↺ 0.19×
logfmt count by level, filter, drop 1h 239 ms 255 ms 802 ms 0.32×
logfmt volume 1h 118 ms 114 ms 1.04 s 0.11×
logs plain 1h 44 ms 59 ms 86 ms 0.69×
logs | json 1h 54 ms 40 ms 55 ms 0.73×
logs | logfmt 1h 79 ms 78 ms 81 ms 0.96×
plain volume 1h 9 ms 9 ms 569 ms 0.02×
stream-label count by app 1h 13 ms 13 ms 505 ms 0.03×
topk rate by app 1h 25 ms 23 ms 559 ms 0.04×
ungrouped rate 1h 6 ms 6 ms 498 ms 0.01×
DB3 json field breakdown user_id 1h 32 ms 26 ms 340 ms 0.08×
DB4 json field breakdown batch_id 1h 63 ms 76 ms 347 ms 0.22×
X1 json filter level_extracted, by level 1h 1.87 s 1.88 s 413 ms 4.56×
X2 json group by level_extracted 1h 158 ms 170 ms 446 ms 0.38×
X3 json rate, level_extracted equality 1h 150 ms 143 ms 354 ms 0.40×
X4 json bytes, level_extracted regexp 1h 207 ms 209 ms 425 ms 0.49×
X5 logfmt filter level_extracted, by level 1h 358 ms 358 ms 509 ms 0.70×
E1 logs, json parser over stream labels 1h 84 ms 87 ms 71 ms 1.23×
E2 logs, logfmt parser over stream labels 1h 36 ms 44 ms 32 ms 1.38×
E3 logs, json, existence filter on level_extracted 1h 91 ms 99 ms 121 ms 0.82×
E4 logs, logfmt, existence filter on level_extracted 1h 46 ms 60 ms 43 ms 1.40×
E5 logs, json, drop level_extracted 1h 69 ms 74 ms 39 ms 1.90×
E6 logfmt count by level_extracted 1h 25 ms 25 ms 23 ms 1.09×
E7 json count by level_extracted 1h 756 ms 733 ms 125 ms 5.86×
E8 instant logfmt by level_extracted instant 5 ms 11 ms 8 ms 1.38×
J1 json no-error count by service_name, one service 1h 98 ms 93 ms 115 ms 0.81×
J2 json no-error count by app, namespace 1h 229 ms 231 ms 411 ms 0.56×
J3 json no-error count by parsed method 1h 89 ms 89 ms 96 ms 0.93×
J4 json no-error count by service_name, all services 1h 264 ms 270 ms 482 ms 0.56×
J5 json no-error bytes_rate by app 1h 147 ms 147 ms 376 ms 0.39×
A explore volume, 2 filters, drop 1h 779 ms 736 ms 873 ms 0.84×
B grouped sum, filter, no drop 1h 69 ms 65 ms 464 ms 0.14×
C ungrouped sum, filter, no drop 1h 64 ms 67 ms 380 ms 0.18×
D volume, no filters 1h 109 ms 103 ms 593 ms 0.17×
E field breakdown pipeline (explore) 1h 59 ms 61 ms 391 ms 0.16×
F field breakdown service_version (explore) 1h 705 ms 720 ms 466 ms 1.55×
G field breakdown pipeline (drilldown) 1h 26 ms 24 ms 386 ms 0.06×
H field breakdown service_version (drilldown) 1h 67 ms 65 ms 425 ms 0.15×
I grafana volume of B 1h 59 ms 60 ms 387 ms 0.16×
O2 explore log volume (Grafana form), underscore structured-metadata filter 1h 98 ms 96 ms 351 ms 0.27×
drilldown detected fields 1h 16 ms 26 ms 33 ms 0.79×
drilldown detected labels 1h 17 ms 11 ms 2 ms 5.50×
drilldown landing volume 1h 41 ms 31 ms 53 ms 0.58×
explore label values env 1h 46 ms 45 ms 31 ms 1.45×
explore label values service_name 1h 29 ms 18 ms 37 ms 0.49×
explore labels (label browser) 1h 39 ms 34 ms 15 ms 2.27×
explore labels with selector 1h 39 ms 37 ms 36 ms 1.03×
refresh label values env 1h 3 ms 3 ms 35 ms 0.09×
refresh label values service_name 1h 2 ms 2 ms 39 ms 0.05×
refresh labels (label browser) 1h 2 ms 2 ms 15 ms 0.13×
refresh labels with selector 1h 2 ms 2 ms 44 ms 0.05×
DL1 drilldown field breakdown json-expr, level filter 1h 259 ms 256 ms 356 ms 0.72×
DL2 drilldown field breakdown, level + stream-label filter before json 1h 28 ms 28 ms 22 ms 1.27×
DL3 drilldown logfmt field breakdown, level filter 1h 349 ms 399 ms 702 ms 0.57×
DL4 instant count, level filter, json+logfmt, 1h instant 273 ms 277 ms 764 ms 0.36×
DL6 explore volume, level filter before json 1h 109 ms 97 ms 460 ms 0.21×
PR1 count by level, parenthesised json 1h 107 ms 104 ms 537 ms 0.19×
PR2 rate, parenthesised line filter 1h 28 ms 29 ms 320 ms 0.09×
PR3 bytes by app, parenthesised regexp 1h 31 ms 31 ms 340 ms 0.09×
PR4 count by level, parenthesised parser, offset 1h 116 ms 103 ms 540 ms 0.19×
PR5 rate by app, pipeline after the range 1h 116 ms 115 ms 600 ms 0.19×
PR6 doubly parenthesised range 1h 28 ms 31 ms 316 ms 0.10×
PR7 instant, parenthesised line filter instant 6 ms 5 ms 54 ms 0.09×
A1 nginx include GET, log query 1h 20 ms 13 ms 9 ms 1.44×
A2 nginx exclude GET, log query 1h 13 ms 13 ms 8 ms 1.62×
A3 nginx include GET, count_over_time 1h 10 ms 10 ms 17 ms 0.59×
A4 nginx exclude GET, count_over_time 1h 12 ms 13 ms 25 ms 0.52×
A5 drilldown exclude, volume by detected_level 1h 26 ms 25 ms 21 ms 1.19×
A6 nginx include ends on a literal, log query 1h 11 ms 17 ms 15 ms 1.13×
A7 nginx exclude ends on a literal, log query 1h 17 ms 16 ms 11 ms 1.45×
A8 nginx include two literals, count_over_time 1h 20 ms 14 ms 17 ms 0.82×
A9 nginx include two literals, ends on a literal, count_over_time 1h 15 ms 14 ms 19 ms 0.74×
P1 logs, json, level equals 1h 98 ms 88 ms 103 ms 0.85×
P2 logs, logfmt, level not equal 1h 20 ms 19 ms 25 ms 0.76×
P3 logs, json, level existence 1h 67 ms 69 ms 93 ms 0.74×
P4 logfmt count by level 1h 20 ms 17 ms 27 ms 0.63×
P5 json count by level 1h 50 ms 46 ms 135 ms 0.34×
P6 json count, level filter 1h 9 ms 9 ms 89 ms 0.10×
P7 json rate by level, level regexp 1h 10 ms 10 ms 110 ms 0.09×
P8 json count by a name that is no stream label 1h 54 ms 54 ms 80 ms 0.67×
P9 instant logfmt by level instant 4 ms 6 ms 7 ms 0.86×
E1 json duration_ms sum by service 1h 50 ms 49 ms 130 ms 0.38×
E2 json duration_ms p95 by service 1h 149 ms 149 ms 150 ms 0.99×
E3 logfmt latency_ms rate, production 1h 266 ms 282 ms 730 ms 0.39×
E4 logfmt size_bytes max by service 1h 26 ms 28 ms 141 ms 0.20×
E5 json duration_ms sum, instant instant 21 ms 20 ms 20 ms 1.00×
E6 logfmt ttl unit string (Loki 400) 1h 400 400 400 —
E7 json hexadecimal trace_id (Loki 400) 1h 400 400 400 —
E8 json duration_ms sum by service, namespace prod 1h 101 ms 100 ms 429 ms 0.23×
E9 logfmt latency_ms sum by service, namespace prod 1h 168 ms 169 ms 527 ms 0.32×
D1 logfmt ttl duration_seconds rate by service 1h 37 ms 38 ms 35 ms 1.09×
D2 logfmt ttl duration rate by service 1h 28 ms 40 ms 34 ms 1.18×
D3 logfmt ttl duration_seconds rate, instant instant 5 ms 13 ms 6 ms 2.17×
D4 logfmt ttl duration rate, instant instant 5 ms 12 ms 7 ms 1.71×
G1 logfmt amount sum_over_time, no filter 1h 26 ms 21 ms 65 ms 0.32×
G10 logfmt latency_ms sum_over_time, production 1h 271 ms 269 ms 680 ms 0.40×
G11 logfmt amount sum_over_time, instant instant 6 ms 6 ms 10 ms 0.60×
G12 json duration_ms sum_over_time, instant instant 22 ms 22 ms 22 ms 1.00×
G2 json duration_ms sum_over_time, no filter 1h 46 ms 46 ms 116 ms 0.40×
G3 logfmt amount sum_over_time, error filter 1h 20 ms 22 ms 33 ms 0.67×
G4 json duration_ms sum_over_time, error filter 1h 46 ms 47 ms 109 ms ↺ 0.43×
G5 logfmt amount sum by rate 1h 23 ms 24 ms 33 ms 0.73×
G6 json duration_ms sum by rate 1h 52 ms 52 ms 114 ms 0.46×
G7 duration() of a unit string 1h 36 ms 36 ms 27 ms 1.33×
G8 bytes() of a number 1h 30 ms 35 ms 30 ms 1.17×
G9 json duration_ms max_over_time 1h 51 ms 52 ms 1.06 s 0.05×
U1 json duration_ms sum by service 1h 55 ms 45 ms 129 ms 0.35×
U10 instant sum_over_time unwrap instant 23 ms 20 ms 19 ms 1.05×
U11 instant max_over_time missing label instant 5 ms 4 ms 13 ms 0.31×
U12 json duration_ms over every service 1h 124 ms 126 ms 390 ms 0.32×
U13 logfmt latency_ms over every service 1h 266 ms 275 ms 624 ms 0.44×
U14 duration() of a unit string 1h 26 ms 33 ms 21 ms 1.57×
U2 json duration_ms max by service 1h 52 ms 52 ms 494 ms 0.11×
U3 json duration_ms sliding sum 1h 42 ms 48 ms 109 ms 0.44×
U4 logfmt size_bytes sum by service 1h 23 ms 25 ms 22 ms 1.14×
U5 logfmt ttl unit string (no sample) 1h 22 ms 26 ms 43 ms 0.60×
U6 missing label, no parser 1h 13 ms 13 ms 44 ms 0.30×
U7 missing label, ungrouped sum 1h 12 ms 12 ms 45 ms 0.27×
U8 rate of an unwrapped label 1h 49 ms 48 ms 102 ms 0.47×
U9 bytes() conversion 1h 29 ms 32 ms 29 ms 1.10×

↺ Loki cold taken from the first set that ran the same query; Loki's cache answered this one.

Why these shapes
  • internal/proxy/unwrap_conversion_error.go → 76: control/instant json filtered, control/instant json volume, control/json rate by level (no filter, no drop), control/json volume no filters, extracted-pushdown/X1 json filter level_extracted, by level, extracted-pushdown/X2 json group by level_extracted, extracted-pushdown/X3 json rate, level_extracted equality, extracted-pushdown/X4 json bytes, level_extracted regexp …
  • internal/proxy/query_translation.go → 38: control/bytes_over_time by app, control/instant json volume, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logfmt count by level, filter, drop, control/logfmt volume, control/logs | json …
  • internal/proxy/range_metric_compat.go → 21: control/bytes_over_time by app, control/instant json volume, control/instant ungrouped rate, control/json volume no filters, control/stream-label count by app, control/topk rate by app, control/ungrouped rate, json-filter-pushdown/A explore volume, 2 filters, drop …
  • go.mod → 20: control/by service_name, control/bytes_over_time by app, control/detected_level volume, control/drilldown detected_level, control/drilldown level volume, control/drilldown service landing, control/instant json filtered, control/instant json volume …
  • go.sum → 20: control/by service_name, control/bytes_over_time by app, control/detected_level volume, control/drilldown detected_level, control/drilldown level volume, control/drilldown service landing, control/instant json filtered, control/instant json volume …
  • internal/translator/unwrap_gate.go → 12: unwrap-gate/G1 logfmt amount sum_over_time, no filter, unwrap-gate/G10 logfmt latency_ms sum_over_time, production, unwrap-gate/G11 logfmt amount sum_over_time, instant, unwrap-gate/G12 json duration_ms sum_over_time, instant, unwrap-gate/G2 json duration_ms sum_over_time, no filter, unwrap-gate/G3 logfmt amount sum_over_time, error filter, unwrap-gate/G4 json duration_ms sum_over_time, error filter, unwrap-gate/G5 logfmt amount sum by rate …
  • shape added or edited → 12: unwrap-gate/G1 logfmt amount sum_over_time, no filter, unwrap-gate/G10 logfmt latency_ms sum_over_time, production, unwrap-gate/G11 logfmt amount sum_over_time, instant, unwrap-gate/G12 json duration_ms sum_over_time, instant, unwrap-gate/G2 json duration_ms sum_over_time, no filter, unwrap-gate/G3 logfmt amount sum_over_time, error filter, unwrap-gate/G4 json duration_ms sum_over_time, error filter, unwrap-gate/G5 logfmt amount sum by rate …
  • internal/proxy/backend.go → 11: metadata-inventory/drilldown detected fields, metadata-inventory/drilldown detected labels, metadata-inventory/drilldown landing volume, metadata-inventory/explore label values env, metadata-inventory/explore label values service_name, metadata-inventory/explore labels (label browser), metadata-inventory/explore labels with selector, metadata-inventory/refresh label values env …
  • control smoke → 8: control/drilldown service landing, control/instant ungrouped rate, control/json rate by level (no filter, no drop), control/json volume no filters, control/logs plain, control/logs | json, control/stream-label count by app, control/topk rate by app

base 4e4f3e8a56 → PR 7b230a3c33 · 4 runs (first is cold), warm p50 · ranges 1h · noise ±30% and 100 ms · VictoriaLogs restarts 0→0 · 11.9 min. Same runner, one fresh stack, targets interleaved per request. How to read and reproduce: bench/ab/README.md.

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

PR Quality Report

Compared against base branch main.

Coverage and tests

Signal Base PR Delta
Test count 0 0 0
Coverage 0% 0% n/a

Compatibility

Track Base PR Delta
Loki API 100.0% 11/11 (100.0%) 0.0% (stable)
Logs Drilldown 100.0% 17/17 (100.0%) 0.0% (stable)
VictoriaLogs 100.0% 11/11 (100.0%) 0.0% (stable)

Performance smoke

Lower CPU cost (ns/op) is better. Lower benchmark memory cost (B/op, allocs/op) is better. Higher throughput is better. Lower load-test memory growth is better. Benchmark rows are medians from repeated samples.

Signal Base PR Delta
QueryRange cache-hit CPU cost 3377.0 ns/op 3391.0 ns/op +0.4% (stable)
QueryRange cache-hit memory 1240.0 B/op 1240.0 B/op 0.0% (stable)
QueryRange cache-hit allocations 27.0 allocs/op 27.0 allocs/op 0.0% (stable)
QueryRange cache-bypass CPU cost 3948.0 ns/op 3979.0 ns/op +0.8% (stable)
QueryRange cache-bypass memory 1301.0 B/op 1299.0 B/op -0.2% (stable)
QueryRange cache-bypass allocations 27.0 allocs/op 27.0 allocs/op 0.0% (stable)
Labels cache-hit CPU cost 969.5 ns/op 1017.0 ns/op +4.9% (stable)
Labels cache-hit memory 48.0 B/op 48.0 B/op 0.0% (stable)
Labels cache-hit allocations 3.0 allocs/op 3.0 allocs/op 0.0% (stable)
Labels cache-bypass CPU cost 1579.0 ns/op 1654.0 ns/op +4.7% (stable)
Labels cache-bypass memory 400.0 B/op 400.0 B/op 0.0% (stable)
Labels cache-bypass allocations 14.0 allocs/op 14.0 allocs/op 0.0% (stable)

State

  • Coverage, compatibility, and sampled performance are reported here from the same PR workflow.
  • This is a delta report, not a release gate by itself. Required checks still decide merge safety.
  • Performance is a smoke comparison, not a full benchmark lab run.
  • Delta states use the same noise guards as the quality gate (percent + absolute + low-baseline checks), so report labels match merge-gate behavior.

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

✅ Visual smoke: passed

Base 4e4f3e8a56 vs PR 7e4575e8ab, Grafana through the proxy builds and Loki on one fresh stack; run.
Core set: 5 capture(s) at 1h. Detailed set: 27 entries (explore-metric-plain, explore-A, explore-B, explore-C, explore-E, explore-F, explore-logs-json, explore-logfmt-metric, ...). 33 capture(s), 15m/1h/6h, 24h and 7d stay out of PR runs.
Trimmed to the 1h range for the capture budget: explore-A, explore-B, explore-C, explore-E, explore-F, explore-logs-json, explore-logfmt-metric, explore-logs-extracted, explore-extracted-metric, explore-extracted-filter-metric, explore-extracted-group-metric, explore-logs-plain-level, explore-plain-level-metric, explore-metric-parenthesized, explore-unwrap-sum, explore-unwrap-unit-string, explore-unwrap-missing-label, explore-unwrap-conversion-error, explore-unwrap-duration-seconds, explore-pattern-filter-anchored, explore-unwrap-quantile-over-time, explore-json-error-filter-metric, explore-unwrap-max-over-time-end-sample, dd-field-service_version, dd-field-pipeline.

page range set base = PR (data) vs Loki (PR) pixel diff result
dd-fields 1h core ✅ identical identical (4 explained, 1 history-dependent) 0.11% ✅
dd-landing 1h core ✅ identical identical (3 explained) 0.14% ✅
dd-service-logs 1h core ✅ identical identical (3 explained, 1 history-dependent) 0.11% ✅
explore-logs-plain 1h core ✅ identical identical (1 explained) 0.07% ✅
explore-metric-plain 1h core ✅ identical identical (1 explained) 0.04% ✅
dd-field-pipeline 1h detailed ✅ identical identical (3 explained, 1 history-dependent) 0.11% ✅
dd-field-service_version 1h detailed ✅ identical identical (3 explained, 1 history-dependent) 0.11% ✅
explore-A 1h detailed ✅ identical identical (1 explained) 0.04% ✅
explore-B 1h detailed ✅ identical identical (1 explained) 0.04% ✅
explore-C 1h detailed ✅ identical identical (1 explained) 0.04% ✅
explore-E 1h detailed ✅ identical identical (1 explained) 0.04% ✅
explore-F 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-extracted-filter-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-extracted-group-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-extracted-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-json-error-filter-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-logfmt-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-logs-extracted 1h detailed ✅ identical 1 difference(s), 1 on base too 0.07% ✅
explore-logs-json 1h detailed ✅ identical 1 difference(s), 1 on base too 0.07% ✅
explore-logs-plain-level 1h detailed ✅ identical 1 difference(s), 1 on base too; 1 explained 0.07% ✅
explore-metric-parenthesized 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-metric-plain 15m detailed ✅ identical identical (1 explained) 0.04% ✅
explore-metric-plain 6h detailed ✅ identical n/a (beyond the Loki window) 0.04% ✅
explore-pattern-filter-anchored 1h detailed ✅ identical 1 difference(s), 1 on base too 0.07% ✅
explore-plain-level-metric 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-conversion-error 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-duration-seconds 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-max-over-time-end-sample 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-missing-label 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-quantile-over-time 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-sum 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
explore-unwrap-unit-string 1h detailed ✅ identical 1 difference(s), 1 on base too 0.04% ✅
tail-json-filter live detailed ✅ identical entries in the window: PR 1829, Loki 633, in both 633 5.31% ✅

Gate: a base-vs-PR data difference passes when the PR is closer to Loki, and fails when the PR diverges from Loki or Loki cannot decide it (at a range Loki does not hold, a difference passes when the same page proved it closer to Loki at a shorter range; otherwise label the pull request visual-change-expected to accept an intended change). Also failing: a panel empty on the PR but not on the base, an error banner, panel error, error answer or new "No data" panel on the PR, a PR side that never settled, a difference that did not reproduce on the recapture. Pixel differences only warn.

Core set montages (5): base | PR | Loki

dd-fields-1h

dd-fields-1h

dd-landing-1h

dd-landing-1h

dd-service-logs-1h

dd-service-logs-1h

explore-logs-plain-1h

explore-logs-plain-1h

explore-metric-plain-1h

explore-metric-plain-1h

All montages of this run: pr-visuals/pr-730.

@github-actions github-actions Bot added size/XL Extra large change and removed size/XL Extra large change labels Oct 12, 2026
@szibis
szibis force-pushed the fix/unwrap-gate-parsefloat branch from 2d738b7 to 4269be1 Compare October 12, 2026 11:01
@github-actions github-actions Bot added size/XL Extra large change and removed size/XL Extra large change labels Oct 12, 2026
@szibis
szibis marked this pull request as ready for review October 12, 2026 13:23
@szibis
szibis force-pushed the fix/unwrap-gate-parsefloat branch from 53041df to 0584a2d Compare October 22, 2026 14:26
@github-actions github-actions Bot added size/XL Extra large change and removed size/XL Extra large change labels Oct 22, 2026
@szibis
szibis force-pushed the fix/unwrap-gate-parsefloat branch from 0584a2d to 7c0d042 Compare October 22, 2026 20:35
@github-actions github-actions Bot added size/XL Extra large change and removed size/XL Extra large change labels Oct 22, 2026
…verflows fails the query like Loki

The unwrap gate now has the syntax of strconv.ParseFloat (hexadecimal floats, digit
separators in exponents, exponent 308, infinity), time.ParseDuration (leading zeros)
and humanize.ParseBytes (commas anywhere in the number, any Unicode space around the
unit). A range filter after the math pipe drops the values whose conversion overflows,
the counting gate flags them so the query fails with SampleExtractionErr, and nan
values are applied to the answer as NaN where every function of the query poisons its
result.
…n-only check shares the backend call

The flagged rows' NaN is read as 0 with the math pipe's default operator instead of a
format pipe, which rewrote the converted column as text that the stats read only up to
27 characters. A check that only counts nan values no longer separates identical
concurrent stats calls. Adds infinity and instant nan answers to the handler tests.
… grammar refused

The counting gate tests the grammar once per row and tells infinity (flag 3) and nan
(flag 2) apart only on the rows it refused; the plain gate selects infinity with a
case-insensitive phrase filter instead of a regular expression per row. The duration
limit is written without an exponent, go-humanize v1.0.1 checks the bytes port in the
property test, the value-forms e2e matrix is trimmed for the shared group, a probe-less
check is covered by a coalescing test, and the conversion-error row reports overflow.
…ouped instants

The over-time functions answer query_range one step early or with the step's bucket instead of the window (semantics/unwrap-over-time-range-windows) and an ungrouped series lacks the parsed labels Loki keeps (semantics/unwrap-per-series-identity-raw-rows), so comparing their range answers said nothing about the value gate: every spelling failed on main and on this branch alike. The rate stays on query_range; sum, avg, max and min over time are compared on instant queries grouped by the service, which match Loki. A window that holds only a nan under max or min stays a registered residual.

The ungrouped instant sum over sum_over_time or avg_over_time of a nan series still differs from Loki (8 subtests, identical on main) and is added to the registry row.
Twelve shapes (plain unwrap, with an error filter, sum by over rate, duration() and bytes(), max, every production stream, instant) over 1h with Loki and 6h, 24h and 7d against main, 5 runs, plus a 7-run re-measure of the duration, bytes and plain shapes and the control set at 1h and 24h. Every verdict is within the harness noise; a duration() unwrap is 8 to 16 ms slower on a 60 to 80 ms query. The performance evidence and gap reports are regenerated.
The registry row and the changelog entry carry the measured cost of the gate (A/B against main), the ungrouped instant sum over a nan series that still differs from Loki, and the live run on VictoriaLogs v1.40.0, v1.47.0 and v1.52.0.
…arity run

An instant sum without grouping over sum_over_time or avg_over_time of a series that holds a nan is a registered residual of the unwrap gate (semantics/unwrap-gate-parsefloat-divergences): Loki answers NaN and the proxy drops the series. The parity test compared it and failed on the stack, so it skips that outer for those two functions, like the other residuals it already skips.
@szibis
szibis force-pushed the fix/unwrap-gate-parsefloat branch from 3b1958e to 7e4575e Compare October 22, 2026 21:10
@github-actions github-actions Bot added size/XL Extra large change and removed size/XL Extra large change labels Oct 22, 2026
@szibis
szibis merged commit 06f809f into main Oct 23, 2026
77 checks passed
@szibis
szibis deleted the fix/unwrap-gate-parsefloat branch October 23, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix scope/docs Documentation scope/proxy Proxy core scope/tests Tests scope/translator LogQL translator size/XL Extra large change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant