implement label_join issue 1147 (#2806)

Replace OptionalArgs int with Variadic int.
This commit is contained in:
Harsh Agarwal 2017-06-16 19:21:22 +05:30 committed by Brian Brazil
parent c89f8753f5
commit 16867c89a7
4 changed files with 151 additions and 52 deletions

View File

@ -18,6 +18,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -28,11 +29,11 @@ import (
// Function represents a function of the expression language and is // Function represents a function of the expression language and is
// used by function nodes. // used by function nodes.
type Function struct { type Function struct {
Name string Name string
ArgTypes []model.ValueType ArgTypes []model.ValueType
OptionalArgs int Variadic int
ReturnType model.ValueType ReturnType model.ValueType
Call func(ev *evaluator, args Expressions) model.Value Call func(ev *evaluator, args Expressions) model.Value
} }
// === time() model.SampleValue === // === time() model.SampleValue ===
@ -849,6 +850,50 @@ func funcLabelReplace(ev *evaluator, args Expressions) model.Value {
return vector 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 === // === vector(s scalar) Vector ===
func funcVector(ev *evaluator, args Expressions) model.Value { func funcVector(ev *evaluator, args Expressions) model.Value {
return vector{ return vector{
@ -986,25 +1031,25 @@ var functions = map[string]*Function{
Call: funcCountScalar, Call: funcCountScalar,
}, },
"days_in_month": { "days_in_month": {
Name: "days_in_month", Name: "days_in_month",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcDaysInMonth, Call: funcDaysInMonth,
}, },
"day_of_month": { "day_of_month": {
Name: "day_of_month", Name: "day_of_month",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcDayOfMonth, Call: funcDayOfMonth,
}, },
"day_of_week": { "day_of_week": {
Name: "day_of_week", Name: "day_of_week",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcDayOfWeek, Call: funcDayOfWeek,
}, },
"delta": { "delta": {
Name: "delta", Name: "delta",
@ -1049,11 +1094,11 @@ var functions = map[string]*Function{
Call: funcHoltWinters, Call: funcHoltWinters,
}, },
"hour": { "hour": {
Name: "hour", Name: "hour",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcHour, Call: funcHour,
}, },
"idelta": { "idelta": {
Name: "idelta", Name: "idelta",
@ -1079,6 +1124,13 @@ var functions = map[string]*Function{
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcLabelReplace, 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": { "ln": {
Name: "ln", Name: "ln",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
@ -1110,18 +1162,18 @@ var functions = map[string]*Function{
Call: funcMinOverTime, Call: funcMinOverTime,
}, },
"minute": { "minute": {
Name: "minute", Name: "minute",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcMinute, Call: funcMinute,
}, },
"month": { "month": {
Name: "month", Name: "month",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcMonth, Call: funcMonth,
}, },
"predict_linear": { "predict_linear": {
Name: "predict_linear", Name: "predict_linear",
@ -1148,11 +1200,11 @@ var functions = map[string]*Function{
Call: funcResets, Call: funcResets,
}, },
"round": { "round": {
Name: "round", Name: "round",
ArgTypes: []model.ValueType{model.ValVector, model.ValScalar}, ArgTypes: []model.ValueType{model.ValVector, model.ValScalar},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcRound, Call: funcRound,
}, },
"scalar": { "scalar": {
Name: "scalar", Name: "scalar",
@ -1209,11 +1261,11 @@ var functions = map[string]*Function{
Call: funcVector, Call: funcVector,
}, },
"year": { "year": {
Name: "year", Name: "year",
ArgTypes: []model.ValueType{model.ValVector}, ArgTypes: []model.ValueType{model.ValVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: model.ValVector, ReturnType: model.ValVector,
Call: funcYear, Call: funcYear,
}, },
} }

View File

@ -1086,13 +1086,23 @@ func (p *parser) checkType(node Node) (typ model.ValueType) {
case *Call: case *Call:
nargs := len(n.Func.ArgTypes) nargs := len(n.Func.ArgTypes)
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) { if n.Func.Variadic == 0 {
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 %d argument(s) in call to %q, got %d", nargs, 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)) } 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 { 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)) p.expectType(arg, n.Func.ArgTypes[i], fmt.Sprintf("call to function %q", n.Func.Name))
} }

View File

@ -1356,11 +1356,11 @@ var testExpr = []struct {
}, { }, {
input: "floor()", input: "floor()",
fail: true, 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)", input: "floor(some_metric, other_metric)",
fail: true, 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)", input: "floor(1)",
fail: true, fail: true,

View File

@ -223,6 +223,43 @@ eval_fail instant at 0m label_replace(testmetric, "src", "", "", "")
clear 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. # Tests for vector.
eval instant at 0m vector(1) eval instant at 0m vector(1)
{} 1 {} 1