Skip to content

Add yrate/yincrease/ydelta (2.55)#22

Draft
ColinDKelley wants to merge 6 commits into
invoca-2.55.1/fix-xrate-extended-rangefrom
invoca-2.55.1/add-yrate
Draft

Add yrate/yincrease/ydelta (2.55)#22
ColinDKelley wants to merge 6 commits into
invoca-2.55.1/fix-xrate-extended-rangefrom
invoca-2.55.1/add-yrate

Conversation

@ColinDKelley
Copy link
Copy Markdown

Summary

Forward-port of #17 onto invoca-2.55.1/fix-xrate-extended-range.

All four commits cherry-picked cleanly from invoca-2.53.1/add-yrate; promql/functions.go, promql/parser/functions.go, and promql/promqltest/testdata/functions.test all auto-merged. No code changes from the 2.53.1 version — the 2.53→2.55 upstream delta is entirely in other subsystems (OTLP, remote write, UI) and does not touch the range-extension path.

  1. Add yIncrease + rangeFromSelectors helpers.
  2. Add yrate / yincrease / ydelta PromQL functions.
  3. Add REPLACE_RATE_FUNCS env switch for yrate; run once at init — supports x / X (point rate→xrate, keep x* names available) and 2 / y / Y (point rate→yrate, keep y* names available).
  4. Add yrate/yincrease/ydelta test cases with eval times shifted off the sample cadence to keep samples strictly inside the half-open [rangeStart, rangeEnd) interval.

Range-boundary reminder: 2.55 still uses the 2.x closed-closed [start, end] range selector semantics, so this port requires no expected-value changes. Prometheus 3.0 flips to left-open (start, end] (upstream issue prometheus#13213), which will require a re-examination of yIncrease at that cutover.

Test plan

  • go test -count=1 ./promql/... passes on Go 1.26.2 (with and without REPLACE_RATE_FUNCS=y)
  • All expected values reproduce identically to 2.53.1's invoca-2.53.1/add-yrate

Related

Once #21 lands on invoca-2.55.1-base, this PR's base can be retargeted to invoca-2.55.1-base directly.

Ported from invoca-2.39.2-extensions and adapted to the 2.53 FPoint API.

yIncrease computes the per-range increase using a simple linear
algorithm that assumes one extended-range sample is kept before the
range start (mirrors the mechanism xrate already uses via
ExtRange: true). Handles counter-reset skew when isCounter is true.

rangeFromSelectors consolidates the boilerplate for unpacking the
matrix-selector arguments shared by yincrease/yrate/ydelta.

Both are unexported and not yet wired up; the follow-up commit adds
the funcY* wrappers and parser entries.

Made-with: Cursor
Adds three new range-vector functions that mirror the shape of the
existing xrate/xincrease/xdelta family:

  - ydelta(m[r])    for gauges: last-in-range minus last-before-range
  - yincrease(m[r]) for counters: yIncrease with isCounter=true
  - yrate(m[r])     same as yincrease, divided by range seconds

The funcY* wrappers delegate to the yIncrease helper added in the
previous commit. Parser entries use ExtRange: true to opt into the
extended-range window populated by matrixIterSlice.

Smoke-verified with a small loaded-storage test: yrate/yincrease/
ydelta match expected values derived from the yIncrease algorithm
(last_in_range - last_before_range + counter_reset_skew), without
requiring any engine changes beyond what xrate already relies on.

No REPLACE_RATE_FUNCS wiring yet; that lands in a follow-up commit.

Made-with: Cursor
Extends the existing REPLACE_RATE_FUNCS init-time hook to also
recognise "x"/"X" and "2"/"y"/"Y" values (matching the invoca-2.39.2
extension). The "x" and "y" cases repoint rate/increase/delta at the
xrate or yrate implementations respectively, while keeping the x*/y*
names available under their original spellings.

Also introduces repointParserFunctions and repointFunction as small
helpers so the dispatch-table rewrites read cleanly.

Made-with: Cursor
Ports the full set of yrate test expectations from
invoca-2.39.2-extensions, split across three existing sections of
promql/promqltest/testdata/functions.test:

  - Rate-vs-xrate showcase block: yrate() evals added in parallel with
    each rate/xrate variant, so readers can see yrate's pre-origin-
    treated-as-zero behaviour next to the other two semantics.

  - "Tests for xincrease/xrate" block: replaced the 0+10x10 /
    0+10x5 0+10x4 load with the 1000+10x10 / 2000+10x5 5+10x4 load
    from 2.39.2. Absolute offsets make the yrate family's per-range
    values visibly diverge from xrate (e.g. yincrease[50s] returns
    1090 for /foo while xincrease[50s] returns 90). Re-adds
    increase() coverage for the same data as a standard-Prometheus
    reference.

  - Counter-reset block: switched from 0 1 2 3 2 3 4 to the 2.39.2
    1000 1001 1003 1006 4 9 16 load, added yincrease coverage at
    the 30m / 10m / 5m ranges and at evaluation times that straddle
    the counter-reset boundary.

  - New ydelta block: exercises ydelta on two mirrored gauge series
    (one increasing, one decreasing) at 30m / 25m / 5m ranges with
    reference delta()/xdelta() comparisons.

Eval times are shifted one second (49s instead of 50s) or one minute
(29m instead of 30m) off the sample cadence so that sample timestamps
land strictly inside the half-open [rangeStart, rangeEnd) interval.
When samples align exactly on a range boundary, matrixIterSlice treats
the sample as the retained pre-range sample, which hides the pre-range
counter value from yIncrease; the shifted eval times mirror the
+321ms offset the legacy promql.NewTest framework applied for the
same reason. All ported 2.39.2 expected values reproduce exactly
under the new promqltest framework with this offset.

Made-with: Cursor
ColinDKelley and others added 2 commits April 19, 2026 15:41
The yrate/yincrease/ydelta family is designed so that two adjacent
windows over a series partition a wider window without double-counting
any sample. Restated as an invariant: for any three timestamps
T_0 < T_1 < T_2 with r_1 = T_1 - T_0 and r_2 = T_2 - T_1,

  yincrease(m[r_1]) @ T_1  +  yincrease(m[r_2]) @ T_2
    ==
  yincrease(m[r_1 + r_2]) @ T_2

This property is what makes yincrease safe to aggregate across
adjacent query ranges and what distinguishes it from stock
rate/increase. Until now it was only covered implicitly through the
1000+/2000+ numeric regression block, which made silent semantic
regressions plausible (e.g. a boundary flip that preserves individual
queries' values but breaks additivity).

Add three direct scenarios that assert LHS and RHS separately so any
drift is visible side-by-side:

  1. uniform counter, no resets
  2. counter reset inside the earlier window
  3. counter reset inside the later window

All three pick T_0, T_1, T_2 off-cadence so no sample lands on a
range boundary. That makes the expected values stable across both
boundary conventions (left-inclusive on the add-yrate line,
right-inclusive after align-yrate-to-3x-range-boundary) and lets the
block cherry-pick cleanly up the yrate branch stack.

Made-with: Cursor
Match the upstream-facing proposal vocabulary by retiring "linear" /
"linearity" from yrate-related internal artifacts in favor of:

* "additive over adjacent ranges" -- the precise mathematical anchor
  (finite additivity over a partition of disjoint intervals)
* "composable" -- the user-facing benefit derived from additivity
  (already the term PROM-52 names as a goal for `anchored`)

Two atomic edits, no behavior change:

* promql/functions.go: yIncrease docstring now says "additive over
  adjacent periods, and therefore composable across any partitioning
  of a wider range into contiguous sub-ranges". The formula stays
  the same.

* promql/promqltest/testdata/functions.test: rename the test-data
  block header from "Linearity invariant" to "Additivity invariant",
  expand the introduction to mention composability, and rename the
  three test series from linearity_{uniform,reset_early,reset_late}
  to additivity_{uniform,reset_early,reset_late}.

Strict "linearity" overclaimed (the property at issue is finite
additivity over a partition of adjacent intervals, not full linearity
over a vector space) and the two-term split keeps the proof anchor
distinct from the user-facing benefit.

Unrelated upstream references to "linear" (predict_linear,
linearRegression, "linear search" complexity comments) are left
untouched.

Co-authored-by: Cursor <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant