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"
"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,
},
}

View file

@ -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))
}

View file

@ -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,

View file

@ -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