promql: faster range-query of label_replace and label_join

These functions act on the labels only, so don't need to go step by step
over the samples in a range query.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
This commit is contained in:
Bryan Boreham 2024-02-04 11:08:20 +01:00
parent 805d51f6d3
commit fdd5b85e06
2 changed files with 57 additions and 81 deletions

View File

@ -1409,6 +1409,15 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
break break
} }
} }
// Special handling for functions that work on series not samples.
switch e.Func.Name {
case "label_replace":
return ev.evalLabelReplace(e.Args)
case "label_join":
return ev.evalLabelJoin(e.Args)
}
if !matrixArg { if !matrixArg {
// Does not have a matrix argument. // Does not have a matrix argument.
return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) { return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {

View File

@ -1321,59 +1321,47 @@ func funcChanges(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelp
return append(enh.Out, Sample{F: float64(changes)}), nil return append(enh.Out, Sample{F: float64(changes)}), nil
} }
// === label_replace(Vector parser.ValueTypeVector, dst_label, replacement, src_labelname, regex parser.ValueTypeString) (Vector, Annotations) === // label_replace function operates only on series; does not look at timestamps or values.
func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { func (ev *evaluator) evalLabelReplace(args parser.Expressions) (parser.Value, annotations.Annotations) {
var ( var (
vector = vals[0].(Vector)
dst = stringFromArg(args[1]) dst = stringFromArg(args[1])
repl = stringFromArg(args[2]) repl = stringFromArg(args[2])
src = stringFromArg(args[3]) src = stringFromArg(args[3])
regexStr = stringFromArg(args[4]) regexStr = stringFromArg(args[4])
) )
if enh.regex == nil { regex, err := regexp.Compile("^(?:" + regexStr + ")$")
var err error if err != nil {
enh.regex, err = regexp.Compile("^(?:" + regexStr + ")$") panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
if err != nil { }
panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr)) if !model.LabelNameRE.MatchString(dst) {
} panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
if !model.LabelNameRE.MatchString(dst) {
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
}
enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out))
} }
for _, el := range vector { val, ws := ev.eval(args[0])
h := el.Metric.Hash() matrix := val.(Matrix)
var outMetric labels.Labels lb := labels.NewBuilder(labels.EmptyLabels())
if l, ok := enh.Dmn[h]; ok {
outMetric = l
} else {
srcVal := el.Metric.Get(src)
indexes := enh.regex.FindStringSubmatchIndex(srcVal)
if indexes == nil {
// If there is no match, no replacement should take place.
outMetric = el.Metric
enh.Dmn[h] = outMetric
} else {
res := enh.regex.ExpandString([]byte{}, repl, srcVal, indexes)
lb := labels.NewBuilder(el.Metric).Del(dst) for i, el := range matrix {
if len(res) > 0 { srcVal := el.Metric.Get(src)
lb.Set(dst, string(res)) indexes := regex.FindStringSubmatchIndex(srcVal)
} if indexes != nil { // Only replace when regexp matches.
outMetric = lb.Labels() res := regex.ExpandString([]byte{}, repl, srcVal, indexes)
enh.Dmn[h] = outMetric lb.Reset(el.Metric)
} lb.Set(dst, string(res))
matrix[i].Metric = lb.Labels()
} }
enh.Out = append(enh.Out, Sample{
Metric: outMetric,
F: el.F,
H: el.H,
})
} }
return enh.Out, nil if matrix.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
return matrix, ws
}
// === label_replace(Vector parser.ValueTypeVector, dst_label, replacement, src_labelname, regex parser.ValueTypeString) (Vector, Annotations) ===
func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
panic("funcLabelReplace wrong implementation called")
} }
// === Vector(s Scalar) (Vector, Annotations) === // === Vector(s Scalar) (Vector, Annotations) ===
@ -1385,19 +1373,13 @@ func funcVector(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe
}), nil }), nil
} }
// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) (Vector, Annotations) === // label_join function operates only on series; does not look at timestamps or values.
func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { func (ev *evaluator) evalLabelJoin(args parser.Expressions) (parser.Value, annotations.Annotations) {
var ( var (
vector = vals[0].(Vector)
dst = stringFromArg(args[1]) dst = stringFromArg(args[1])
sep = stringFromArg(args[2]) sep = stringFromArg(args[2])
srcLabels = make([]string, len(args)-3) srcLabels = make([]string, len(args)-3)
) )
if enh.Dmn == nil {
enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out))
}
for i := 3; i < len(args); i++ { for i := 3; i < len(args); i++ {
src := stringFromArg(args[i]) src := stringFromArg(args[i])
if !model.LabelName(src).IsValid() { if !model.LabelName(src).IsValid() {
@ -1406,42 +1388,27 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe
srcLabels[i-3] = src srcLabels[i-3] = src
} }
if !model.LabelName(dst).IsValid() { val, ws := ev.eval(args[0])
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst)) matrix := val.(Matrix)
}
srcVals := make([]string, len(srcLabels)) srcVals := make([]string, len(srcLabels))
for _, el := range vector { lb := labels.NewBuilder(labels.EmptyLabels())
h := el.Metric.Hash()
var outMetric labels.Labels
if l, ok := enh.Dmn[h]; ok {
outMetric = l
} else {
for i, src := range srcLabels { for i, el := range matrix {
srcVals[i] = el.Metric.Get(src) for i, src := range srcLabels {
} srcVals[i] = el.Metric.Get(src)
lb := labels.NewBuilder(el.Metric)
strval := strings.Join(srcVals, sep)
if strval == "" {
lb.Del(dst)
} else {
lb.Set(dst, strval)
}
outMetric = lb.Labels()
enh.Dmn[h] = outMetric
} }
strval := strings.Join(srcVals, sep)
enh.Out = append(enh.Out, Sample{ lb.Reset(el.Metric)
Metric: outMetric, lb.Set(dst, strval)
F: el.F, matrix[i].Metric = lb.Labels()
H: el.H,
})
} }
return enh.Out, nil
return matrix, ws
}
// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) (Vector, Annotations) ===
func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
panic("funcLabelReplace wrong implementation called")
} }
// Common code for date related functions. // Common code for date related functions.