Add fill modifier PromQL tests

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-12-10 19:25:08 +01:00
parent af3277f832
commit 57dd1f18b4

View file

@ -0,0 +1,343 @@
# ==================== fill / fill_left / fill_right modifier tests ====================
# Test data for fill modifier tests: vectors with partial overlap.
load 5m
left_vector{label="a"} 10
left_vector{label="b"} 20
left_vector{label="c"} 30
right_vector{label="a"} 100
right_vector{label="b"} 200
right_vector{label="d"} 400
# ---------- Arithmetic operators with fill modifiers ----------
# fill(0): Fill both sides with 0 for addition.
eval instant at 0m left_vector + fill(0) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} 30
{label="d"} 400
# fill_left(0): Only fill left side with 0.
eval instant at 0m left_vector + fill_left(0) right_vector
{label="a"} 110
{label="b"} 220
{label="d"} 400
# fill_right(0): Only fill right side with 0.
eval instant at 0m left_vector + fill_right(0) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} 30
# fill_left and fill_right with different values.
eval instant at 0m left_vector + fill_left(5) fill_right(7) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} 37
{label="d"} 405
# fill with NaN.
eval instant at 0m left_vector + fill(NaN) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} NaN
{label="d"} NaN
# fill with Inf.
eval instant at 0m left_vector + fill(Inf) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} +Inf
{label="d"} +Inf
# fill with -Inf.
eval instant at 0m left_vector + fill(-Inf) right_vector
{label="a"} 110
{label="b"} 220
{label="c"} -Inf
{label="d"} -Inf
# ---------- Comparison operators with fill modifiers ----------
# fill with equality comparison.
eval instant at 0m left_vector == fill(30) right_vector
left_vector{label="c"} 30
# fill with inequality comparison.
eval instant at 0m left_vector != fill(30) right_vector
left_vector{label="a"} 10
left_vector{label="b"} 20
{label="d"} 30
# fill with greater than.
eval instant at 0m left_vector > fill(25) right_vector
left_vector{label="c"} 30
# ---------- Comparison operators with bool modifier and fill ----------
# fill with equality comparison and bool.
eval instant at 0m left_vector == bool fill(30) right_vector
{label="a"} 0
{label="b"} 0
{label="c"} 1
{label="d"} 0
# fill with inequality comparison and bool.
eval instant at 0m left_vector != bool fill(30) right_vector
{label="a"} 1
{label="b"} 1
{label="c"} 0
{label="d"} 1
# fill with greater than and bool.
eval instant at 0m left_vector > bool fill(25) right_vector
{label="a"} 0
{label="b"} 0
{label="c"} 1
{label="d"} 0
# ---------- fill with on() and ignoring() modifiers ----------
clear
load 5m
left_vector{job="foo", instance="a"} 10
left_vector{job="foo", instance="b"} 20
left_vector{job="bar", instance="a"} 30
right_vector{job="foo", instance="a"} 100
right_vector{job="foo", instance="c"} 300
# fill with on().
eval instant at 0m left_vector + on(job, instance) fill(0) right_vector
{job="foo", instance="a"} 110
{job="foo", instance="b"} 20
{job="bar", instance="a"} 30
{job="foo", instance="c"} 300
# fill_right with on().
eval instant at 0m left_vector + on(job, instance) fill_right(0) right_vector
{job="foo", instance="a"} 110
{job="foo", instance="b"} 20
{job="bar", instance="a"} 30
# fill_left with on().
eval instant at 0m left_vector + on(job, instance) fill_left(0) right_vector
{job="foo", instance="a"} 110
{job="foo", instance="c"} 300
# fill with ignoring() - requires group_left since ignoring(job) creates many-to-one matching
# when two left_vector series have same instance but different jobs.
eval instant at 0m left_vector + ignoring(job) group_left fill(0) right_vector
{instance="a", job="foo"} 110
{instance="a", job="bar"} 130
{instance="b", job="foo"} 20
{instance="c"} 300
# ---------- fill with group_left / group_right (many-to-one / one-to-many) ----------
clear
load 5m
requests{method="GET", status="200"} 100
requests{method="POST", status="200"} 200
requests{method="GET", status="500"} 10
requests{method="POST", status="500"} 20
limits{status="200"} 1000
limits{status="404"} 500
limits{status="500"} 50
# group_left with fill_right: fill missing "one" side series.
eval instant at 0m requests / on(status) group_left fill_right(1) limits
{method="GET", status="200"} 0.1
{method="POST", status="200"} 0.2
{method="GET", status="500"} 0.2
{method="POST", status="500"} 0.4
# group_left with fill_left: fill missing "many" side series.
# For status="404", there's no matching requests, so a single series with the match group's labels is filled
eval instant at 0m requests + on(status) group_left fill_left(0) limits
{method="GET", status="200"} 1100
{method="POST", status="200"} 1200
{method="GET", status="500"} 60
{method="POST", status="500"} 70
{status="404"} 500
# group_left with fill on both sides.
eval instant at 0m requests + on(status) group_left fill(0) limits
{method="GET", status="200"} 1100
{method="POST", status="200"} 1200
{method="GET", status="500"} 60
{method="POST", status="500"} 70
{status="404"} 500
# group_right with fill_left: fill missing "one" side series.
clear
load 5m
cpu_info{instance="a", cpu="0"} 1
cpu_info{instance="a", cpu="1"} 1
cpu_info{instance="b", cpu="0"} 1
node_meta{instance="a"} 100
node_meta{instance="c"} 300
# fill_left fills the "one" side (node_meta) when missing for a "many" side series.
eval instant at 0m node_meta * on(instance) group_right fill_left(1) cpu_info
{instance="a", cpu="0"} 100
{instance="a", cpu="1"} 100
{instance="c"} 300
# group_right with fill_right: fill missing "many" side series.
eval instant at 0m node_meta * on(instance) group_right fill_right(0) cpu_info
{instance="a", cpu="0"} 100
{instance="a", cpu="1"} 100
{instance="b", cpu="0"} 0
# group_right with fill on both sides.
eval instant at 0m node_meta * on(instance) group_right fill(1) cpu_info
{instance="a", cpu="0"} 100
{instance="a", cpu="1"} 100
{instance="b", cpu="0"} 1
{instance="c"} 300
# ---------- fill with group_left/group_right and extra labels ----------
clear
load 5m
requests{method="GET", status="200"} 100
requests{method="POST", status="200"} 200
limits{status="200", owner="team-a"} 1000
limits{status="500", owner="team-b"} 50
# group_left with extra label and fill_right.
# Note: when filling the "one" side, the joined label cannot be filled.
eval instant at 0m requests + on(status) group_left(owner) fill_right(0) limits
{method="GET", status="200", owner="team-a"} 1100
{method="POST", status="200", owner="team-a"} 1200
# ---------- Edge cases ----------
clear
load 5m
only_left{label="a"} 10
only_left{label="b"} 20
only_right{label="c"} 30
only_right{label="d"} 40
# No overlap at all - fill creates all results.
eval instant at 0m only_left + fill(0) only_right
{label="a"} 10
{label="b"} 20
{label="c"} 30
{label="d"} 40
# No overlap - fill_left only creates right side results.
eval instant at 0m only_left + fill_left(0) only_right
{label="c"} 30
{label="d"} 40
# No overlap - fill_right only creates left side results.
eval instant at 0m only_left + fill_right(0) only_right
{label="a"} 10
{label="b"} 20
# Complete overlap - fill has no effect.
clear
load 5m
complete_left{label="a"} 10
complete_left{label="b"} 20
complete_right{label="a"} 100
complete_right{label="b"} 200
eval instant at 0m complete_left + fill(99) complete_right
{label="a"} 110
{label="b"} 220
# ---------- fill with range queries ----------
clear
load 5m
range_left{label="a"} 1 2 3 4 5
range_left{label="b"} 10 20 30 40 50
range_right{label="a"} 100 200 300 400 500
range_right{label="c"} 1000 2000 3000 4000 5000
eval range from 0 to 20m step 5m range_left + fill(0) range_right
{label="a"} 101 202 303 404 505
{label="b"} 10 20 30 40 50
{label="c"} 1000 2000 3000 4000 5000
eval range from 0 to 20m step 5m range_left + fill_right(0) range_right
{label="a"} 101 202 303 404 505
{label="b"} 10 20 30 40 50
eval range from 0 to 20m step 5m range_left + fill_left(0) range_right
{label="a"} 101 202 303 404 505
{label="c"} 1000 2000 3000 4000 5000
# Range queries with intermittently present series.
clear
load 5m
intermittent_left{label="a"} 1 _ 3 _ 5
intermittent_left{label="b"} _ 20 _ 40 _
intermittent_right{label="a"} _ 200 _ 400 _
intermittent_right{label="b"} 100 _ 300 _ 500
intermittent_right{label="c"} 1000 _ _ 4000 5000
# When both sides have the same label but are present at different times,
# fill creates results at all timestamps where at least one side is present.
eval range from 0 to 20m step 5m intermittent_left + fill(0) intermittent_right
{label="a"} 1 200 3 400 5
{label="b"} 100 20 300 40 500
{label="c"} 1000 _ _ 4000 5000
# fill_right only fills the right side when it's missing.
# Output only exists when left side is present (right side filled with 0 if missing).
eval range from 0 to 20m step 5m intermittent_left + fill_right(0) intermittent_right
{label="a"} 1 _ 3 _ 5
{label="b"} _ 20 _ 40 _
# fill_left only fills the left side when it's missing.
# Output only exists when right side is present (left side filled with 0 if missing).
eval range from 0 to 20m step 5m intermittent_left + fill_left(0) intermittent_right
{label="a"} _ 200 _ 400 _
{label="b"} 100 _ 300 _ 500
{label="c"} 1000 _ _ 4000 5000
# ---------- fill with vectors where one side is empty ----------
clear
load 5m
non_empty{label="a"} 10
non_empty{label="b"} 20
# Empty right side - fill_right has no effect (nothing to add).
eval instant at 0m non_empty + fill_right(0) nonexistent
{label="a"} 10
{label="b"} 20
# Empty right side - fill_left creates nothing (no right side labels to use).
eval instant at 0m non_empty + fill_left(0) nonexistent
# Empty left side - fill_left has no effect.
eval instant at 0m nonexistent + fill_left(0) non_empty
{label="a"} 10
{label="b"} 20
# Empty left side - fill_right creates nothing.
eval instant at 0m nonexistent + fill_right(0) non_empty
# fill both sides with one side empty.
eval instant at 0m non_empty + fill(0) nonexistent
{label="a"} 10
{label="b"} 20
eval instant at 0m nonexistent + fill(0) non_empty
{label="a"} 10
{label="b"} 20