mirror of
https://github.com/prometheus/prometheus
synced 2024-12-25 07:52:28 +00:00
implement label_join issue 1147 (#2806)
Replace OptionalArgs int with Variadic int.
This commit is contained in:
parent
c89f8753f5
commit
16867c89a7
@ -18,6 +18,7 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
@ -28,11 +29,11 @@ import (
|
||||
// Function represents a function of the expression language and is
|
||||
// used by function nodes.
|
||||
type Function struct {
|
||||
Name string
|
||||
ArgTypes []model.ValueType
|
||||
OptionalArgs int
|
||||
ReturnType model.ValueType
|
||||
Call func(ev *evaluator, args Expressions) model.Value
|
||||
Name string
|
||||
ArgTypes []model.ValueType
|
||||
Variadic int
|
||||
ReturnType model.ValueType
|
||||
Call func(ev *evaluator, args Expressions) model.Value
|
||||
}
|
||||
|
||||
// === time() model.SampleValue ===
|
||||
@ -849,6 +850,50 @@ func funcLabelReplace(ev *evaluator, args Expressions) model.Value {
|
||||
return vector
|
||||
}
|
||||
|
||||
// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) Vector ===
|
||||
func funcLabelJoin(ev *evaluator, args Expressions) model.Value {
|
||||
var (
|
||||
vector = ev.evalVector(args[0])
|
||||
dst = model.LabelName(ev.evalString(args[1]).Value)
|
||||
sep = ev.evalString(args[2]).Value
|
||||
srcLabels = make([]model.LabelName, len(args)-3)
|
||||
)
|
||||
for i := 3; i < len(args); i++ {
|
||||
src := model.LabelName(ev.evalString(args[i]).Value)
|
||||
if !model.LabelNameRE.MatchString(string(src)) {
|
||||
ev.errorf("invalid source label name in label_join(): %s", src)
|
||||
}
|
||||
srcLabels[i-3] = src
|
||||
}
|
||||
|
||||
if !model.LabelNameRE.MatchString(string(dst)) {
|
||||
ev.errorf("invalid destination label name in label_join(): %s", dst)
|
||||
}
|
||||
|
||||
outSet := make(map[model.Fingerprint]struct{}, len(vector))
|
||||
for _, el := range vector {
|
||||
srcVals := make([]string, len(srcLabels))
|
||||
for i, src := range srcLabels {
|
||||
srcVals[i] = string(el.Metric.Metric[src])
|
||||
}
|
||||
|
||||
strval := strings.Join(srcVals, sep)
|
||||
if strval == "" {
|
||||
el.Metric.Del(dst)
|
||||
} else {
|
||||
el.Metric.Set(dst, model.LabelValue(strval))
|
||||
}
|
||||
|
||||
fp := el.Metric.Metric.Fingerprint()
|
||||
if _, exists := outSet[fp]; exists {
|
||||
ev.errorf("duplicated label set in output of label_join(): %s", el.Metric.Metric)
|
||||
} else {
|
||||
outSet[fp] = struct{}{}
|
||||
}
|
||||
}
|
||||
return vector
|
||||
}
|
||||
|
||||
// === vector(s scalar) Vector ===
|
||||
func funcVector(ev *evaluator, args Expressions) model.Value {
|
||||
return vector{
|
||||
@ -986,25 +1031,25 @@ var functions = map[string]*Function{
|
||||
Call: funcCountScalar,
|
||||
},
|
||||
"days_in_month": {
|
||||
Name: "days_in_month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDaysInMonth,
|
||||
Name: "days_in_month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDaysInMonth,
|
||||
},
|
||||
"day_of_month": {
|
||||
Name: "day_of_month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDayOfMonth,
|
||||
Name: "day_of_month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDayOfMonth,
|
||||
},
|
||||
"day_of_week": {
|
||||
Name: "day_of_week",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDayOfWeek,
|
||||
Name: "day_of_week",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcDayOfWeek,
|
||||
},
|
||||
"delta": {
|
||||
Name: "delta",
|
||||
@ -1049,11 +1094,11 @@ var functions = map[string]*Function{
|
||||
Call: funcHoltWinters,
|
||||
},
|
||||
"hour": {
|
||||
Name: "hour",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcHour,
|
||||
Name: "hour",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcHour,
|
||||
},
|
||||
"idelta": {
|
||||
Name: "idelta",
|
||||
@ -1079,6 +1124,13 @@ var functions = map[string]*Function{
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcLabelReplace,
|
||||
},
|
||||
"label_join": {
|
||||
Name: "label_join",
|
||||
ArgTypes: []model.ValueType{model.ValVector, model.ValString, model.ValString, model.ValString},
|
||||
Variadic: -1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcLabelJoin,
|
||||
},
|
||||
"ln": {
|
||||
Name: "ln",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
@ -1110,18 +1162,18 @@ var functions = map[string]*Function{
|
||||
Call: funcMinOverTime,
|
||||
},
|
||||
"minute": {
|
||||
Name: "minute",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcMinute,
|
||||
Name: "minute",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcMinute,
|
||||
},
|
||||
"month": {
|
||||
Name: "month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcMonth,
|
||||
Name: "month",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcMonth,
|
||||
},
|
||||
"predict_linear": {
|
||||
Name: "predict_linear",
|
||||
@ -1148,11 +1200,11 @@ var functions = map[string]*Function{
|
||||
Call: funcResets,
|
||||
},
|
||||
"round": {
|
||||
Name: "round",
|
||||
ArgTypes: []model.ValueType{model.ValVector, model.ValScalar},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcRound,
|
||||
Name: "round",
|
||||
ArgTypes: []model.ValueType{model.ValVector, model.ValScalar},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcRound,
|
||||
},
|
||||
"scalar": {
|
||||
Name: "scalar",
|
||||
@ -1209,11 +1261,11 @@ var functions = map[string]*Function{
|
||||
Call: funcVector,
|
||||
},
|
||||
"year": {
|
||||
Name: "year",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
OptionalArgs: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcYear,
|
||||
Name: "year",
|
||||
ArgTypes: []model.ValueType{model.ValVector},
|
||||
Variadic: 1,
|
||||
ReturnType: model.ValVector,
|
||||
Call: funcYear,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1086,13 +1086,23 @@ func (p *parser) checkType(node Node) (typ model.ValueType) {
|
||||
|
||||
case *Call:
|
||||
nargs := len(n.Func.ArgTypes)
|
||||
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) {
|
||||
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
|
||||
}
|
||||
if nargs < len(n.Args) {
|
||||
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
|
||||
if n.Func.Variadic == 0 {
|
||||
if nargs != len(n.Args) {
|
||||
p.errorf("expected %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
|
||||
}
|
||||
} else {
|
||||
na := nargs - 1
|
||||
if na > len(n.Args) {
|
||||
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
|
||||
} else if nargsmax := na + n.Func.Variadic; n.Func.Variadic > 0 && nargsmax < len(n.Args) {
|
||||
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargsmax, n.Func.Name, len(n.Args))
|
||||
}
|
||||
}
|
||||
|
||||
for i, arg := range n.Args {
|
||||
if i >= len(n.Func.ArgTypes) {
|
||||
i = len(n.Func.ArgTypes) - 1
|
||||
}
|
||||
p.expectType(arg, n.Func.ArgTypes[i], fmt.Sprintf("call to function %q", n.Func.Name))
|
||||
}
|
||||
|
||||
|
@ -1356,11 +1356,11 @@ var testExpr = []struct {
|
||||
}, {
|
||||
input: "floor()",
|
||||
fail: true,
|
||||
errMsg: "expected at least 1 argument(s) in call to \"floor\", got 0",
|
||||
errMsg: "expected 1 argument(s) in call to \"floor\", got 0",
|
||||
}, {
|
||||
input: "floor(some_metric, other_metric)",
|
||||
fail: true,
|
||||
errMsg: "expected at most 1 argument(s) in call to \"floor\", got 2",
|
||||
errMsg: "expected 1 argument(s) in call to \"floor\", got 2",
|
||||
}, {
|
||||
input: "floor(1)",
|
||||
fail: true,
|
||||
|
37
promql/testdata/functions.test
vendored
37
promql/testdata/functions.test
vendored
@ -223,6 +223,43 @@ eval_fail instant at 0m label_replace(testmetric, "src", "", "", "")
|
||||
|
||||
clear
|
||||
|
||||
# Tests for label_join.
|
||||
load 5m
|
||||
testmetric{src="a",src1="b",src2="c",dst="original-destination-value"} 0
|
||||
testmetric{src="d",src1="e",src2="f",dst="original-destination-value"} 1
|
||||
|
||||
# label_join joins all src values in order.
|
||||
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src1", "src2")
|
||||
testmetric{src="a",src1="b",src2="c",dst="a-b-c"} 0
|
||||
testmetric{src="d",src1="e",src2="f",dst="d-e-f"} 1
|
||||
|
||||
# label_join treats non existent src labels as empty strings.
|
||||
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src3", "src1")
|
||||
testmetric{src="a",src1="b",src2="c",dst="a--b"} 0
|
||||
testmetric{src="d",src1="e",src2="f",dst="d--e"} 1
|
||||
|
||||
# label_join overwrites the destination label even if the resulting dst label is empty string
|
||||
eval instant at 0m label_join(testmetric, "dst", "", "emptysrc", "emptysrc1", "emptysrc2")
|
||||
testmetric{src="a",src1="b",src2="c"} 0
|
||||
testmetric{src="d",src1="e",src2="f"} 1
|
||||
|
||||
# test without src label for label_join
|
||||
eval instant at 0m label_join(testmetric, "dst", ", ")
|
||||
testmetric{src="a",src1="b",src2="c"} 0
|
||||
testmetric{src="d",src1="e",src2="f"} 1
|
||||
|
||||
# test without dst label for label_join
|
||||
load 5m
|
||||
testmetric1{src="foo",src1="bar",src2="foobar"} 0
|
||||
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz"} 1
|
||||
|
||||
# label_join creates dst label if not present.
|
||||
eval instant at 0m label_join(testmetric1, "dst", ", ", "src", "src1", "src2")
|
||||
testmetric1{src="foo",src1="bar",src2="foobar",dst="foo, bar, foobar"} 0
|
||||
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz",dst="fizz, buzz, fizzbuzz"} 1
|
||||
|
||||
clear
|
||||
|
||||
# Tests for vector.
|
||||
eval instant at 0m vector(1)
|
||||
{} 1
|
||||
|
Loading…
Reference in New Issue
Block a user