WIP fill() operator

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-11-24 11:34:32 +01:00
parent 61f64a4cb1
commit fcd8db67c1
5 changed files with 711 additions and 599 deletions

View File

@ -2824,7 +2824,8 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
if matching.Card == parser.CardManyToMany {
panic("many-to-many only allowed for set operators")
}
if len(lhs) == 0 || len(rhs) == 0 {
if (len(lhs) == 0 && len(rhs) == 0) ||
((len(lhs) == 0 || len(rhs) == 0) && matching.FillValues.Rhs == nil && matching.FillValues.Lhs == nil) {
return nil, nil // Short-circuit: nothing is going to match.
}
@ -2872,17 +2873,9 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
}
matchedSigs := enh.matchedSigs
// For all lhs samples find a respective rhs sample and perform
// the binary operation.
var lastErr error
for i, ls := range lhs {
sigOrd := lhsh[i].sigOrdinal
rs, found := rightSigs[sigOrd] // Look for a match in the rhs Vector.
if !found {
continue
}
doBinOp := func(ls Sample, rs Sample, sigOrd int) {
// Account for potentially swapped sidedness.
fl, fr := ls.F, rs.F
hl, hr := ls.H, rs.H
@ -2893,7 +2886,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
floatValue, histogramValue, keep, info, err := vectorElemBinop(op, fl, fr, hl, hr, pos)
if err != nil {
lastErr = err
continue
return
}
if info != nil {
lastErr = info
@ -2907,7 +2900,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
floatValue = 0.0
}
case !keep:
continue
return
}
metric := resultMetric(ls.Metric, rs.Metric, op, matching, enh)
if !ev.enableDelayedNameRemoval && returnBool {
@ -2941,6 +2934,44 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
DropName: returnBool,
})
}
// For all lhs samples find a respective rhs sample and perform
// the binary operation.
for i, ls := range lhs {
fmt.Println("processing lhs sample:", ls.Metric.String())
sigOrd := lhsh[i].sigOrdinal
rs, found := rightSigs[sigOrd] // Look for a match in the rhs Vector.
if !found {
if fill := matching.FillValues.Rhs; fill != nil {
fmt.Println("using fill rhs value")
rs = ls
// TODO: Handle histogram fallback values?
rs.F = *fill
} else {
fmt.Println("no match found, continuing")
continue
}
}
doBinOp(ls, rs, sigOrd)
}
// For any rhs samples which have not been matched, check if we need to
// perform the operation with a fill value from the lhs.
if fill := matching.FillValues.Lhs; fill != nil {
for sigOrd, rs := range rightSigs {
if _, matched := matchedSigs[sigOrd]; matched {
continue // Already matched.
}
ls := rs
// TODO: Handle histogram fallback values?
ls.F = *fill
doBinOp(ls, rs, sigOrd)
}
}
return enh.Out, lastErr
}

View File

@ -318,6 +318,19 @@ type VectorMatching struct {
// Include contains additional labels that should be included in
// the result from the side with the lower cardinality.
Include []string
// Fill-in values to use when a series from one side does not find a match on the other side.
FillValues VectorMatchFillValues
}
// VectorMatchFillValues contains the fill values to use for Vector matching
// when one side does not find a match on the other side.
// When a fill value is nil, no fill is applied for that side, and there
// is no output for the match group if there is no match.
type VectorMatchFillValues struct {
// Rhs is the fill value to use for the right-hand side.
Rhs *float64
// Lhs is the fill value to use for the left-hand side.
Lhs *float64
}
// Visitor allows visiting a Node and its child nodes. The Visit method is

View File

@ -139,6 +139,9 @@ BOOL
BY
GROUP_LEFT
GROUP_RIGHT
FILL
LFILL
RFILL
IGNORING
OFFSET
SMOOTHED
@ -189,7 +192,7 @@ START_METRIC_SELECTOR
%type <int> int
%type <uint> uint
%type <float> number series_value signed_number signed_or_unsigned_number
%type <node> step_invariant_expr aggregate_expr aggregate_modifier bin_modifier binary_expr bool_modifier expr function_call function_call_args function_call_body group_modifiers label_matchers matrix_selector number_duration_literal offset_expr anchored_expr smoothed_expr on_or_ignoring paren_expr string_literal subquery_expr unary_expr vector_selector duration_expr paren_duration_expr positive_duration_expr offset_duration_expr
%type <node> step_invariant_expr aggregate_expr aggregate_modifier bin_modifier fallback_modifiers binary_expr bool_modifier expr function_call function_call_args function_call_body group_modifiers label_matchers matrix_selector number_duration_literal offset_expr anchored_expr smoothed_expr on_or_ignoring paren_expr string_literal subquery_expr unary_expr vector_selector duration_expr paren_duration_expr positive_duration_expr offset_duration_expr
%start start
@ -301,7 +304,7 @@ binary_expr : expr ADD bin_modifier expr { $$ = yylex.(*parser).newBinar
// Using left recursion for the modifier rules, helps to keep the parser stack small and
// reduces allocations.
bin_modifier : group_modifiers;
bin_modifier : fallback_modifiers;
bool_modifier : /* empty */
{ $$ = &BinaryExpr{
@ -345,6 +348,28 @@ group_modifiers: bool_modifier /* empty */
}
;
fallback_modifiers: group_modifiers /* empty */
| group_modifiers FILL LEFT_PAREN NUMBER RIGHT_PAREN
{
$$ = $1
fill := yylex.(*parser).number($4.Val)
$$.(*BinaryExpr).VectorMatching.FillValues.Lhs = &fill
$$.(*BinaryExpr).VectorMatching.FillValues.Rhs = &fill
}
| group_modifiers LFILL LEFT_PAREN NUMBER RIGHT_PAREN
{
$$ = $1
fill := yylex.(*parser).number($4.Val)
$$.(*BinaryExpr).VectorMatching.FillValues.Lhs = &fill
}
| group_modifiers RFILL LEFT_PAREN NUMBER RIGHT_PAREN
{
$$ = $1
fill := yylex.(*parser).number($4.Val)
$$.(*BinaryExpr).VectorMatching.FillValues.Rhs = &fill
}
;
grouping_labels : LEFT_PAREN grouping_label_list RIGHT_PAREN
{ $$ = $2 }
@ -953,7 +978,7 @@ counter_reset_hint : UNKNOWN_COUNTER_RESET | COUNTER_RESET | NOT_COUNTER_RESET |
aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | GROUP | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK | LIMITK | LIMIT_RATIO;
// Inside of grouping options label names can be recognized as keywords by the lexer. This is a list of keywords that could also be a label name.
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | ANCHORED | SMOOTHED;
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | FILL | LFILL | RFILL | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | ANCHORED | SMOOTHED;
unary_op : ADD | SUB;
@ -1141,7 +1166,7 @@ offset_duration_expr : number_duration_literal
}
| duration_expr
;
min_max: MIN | MAX ;
duration_expr : number_duration_literal
@ -1248,14 +1273,14 @@ duration_expr : number_duration_literal
;
paren_duration_expr : LEFT_PAREN duration_expr RIGHT_PAREN
{
{
yylex.(*parser).experimentalDurationExpr($2.(Expr))
if durationExpr, ok := $2.(*DurationExpr); ok {
durationExpr.Wrapped = true
$$ = durationExpr
break
}
$$ = $2
$$ = $2
}
;

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,9 @@ var key = map[string]ItemType{
"ignoring": IGNORING,
"group_left": GROUP_LEFT,
"group_right": GROUP_RIGHT,
"fill": FILL,
"lfill": LFILL,
"rfill": RFILL,
"bool": BOOL,
// Preprocessors.