Skip to content

Commit 16867c8

Browse files
sipianbrian-brazil
authored andcommitted
implement label_join issue 1147 (prometheus#2806)
Replace OptionalArgs int with Variadic int.
1 parent c89f875 commit 16867c8

File tree

4 files changed

+151
-52
lines changed

4 files changed

+151
-52
lines changed

promql/functions.go

+97-45
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"regexp"
1919
"sort"
2020
"strconv"
21+
"strings"
2122
"time"
2223

2324
"github.com/prometheus/common/model"
@@ -28,11 +29,11 @@ import (
2829
// Function represents a function of the expression language and is
2930
// used by function nodes.
3031
type Function struct {
31-
Name string
32-
ArgTypes []model.ValueType
33-
OptionalArgs int
34-
ReturnType model.ValueType
35-
Call func(ev *evaluator, args Expressions) model.Value
32+
Name string
33+
ArgTypes []model.ValueType
34+
Variadic int
35+
ReturnType model.ValueType
36+
Call func(ev *evaluator, args Expressions) model.Value
3637
}
3738

3839
// === time() model.SampleValue ===
@@ -849,6 +850,50 @@ func funcLabelReplace(ev *evaluator, args Expressions) model.Value {
849850
return vector
850851
}
851852

853+
// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) Vector ===
854+
func funcLabelJoin(ev *evaluator, args Expressions) model.Value {
855+
var (
856+
vector = ev.evalVector(args[0])
857+
dst = model.LabelName(ev.evalString(args[1]).Value)
858+
sep = ev.evalString(args[2]).Value
859+
srcLabels = make([]model.LabelName, len(args)-3)
860+
)
861+
for i := 3; i < len(args); i++ {
862+
src := model.LabelName(ev.evalString(args[i]).Value)
863+
if !model.LabelNameRE.MatchString(string(src)) {
864+
ev.errorf("invalid source label name in label_join(): %s", src)
865+
}
866+
srcLabels[i-3] = src
867+
}
868+
869+
if !model.LabelNameRE.MatchString(string(dst)) {
870+
ev.errorf("invalid destination label name in label_join(): %s", dst)
871+
}
872+
873+
outSet := make(map[model.Fingerprint]struct{}, len(vector))
874+
for _, el := range vector {
875+
srcVals := make([]string, len(srcLabels))
876+
for i, src := range srcLabels {
877+
srcVals[i] = string(el.Metric.Metric[src])
878+
}
879+
880+
strval := strings.Join(srcVals, sep)
881+
if strval == "" {
882+
el.Metric.Del(dst)
883+
} else {
884+
el.Metric.Set(dst, model.LabelValue(strval))
885+
}
886+
887+
fp := el.Metric.Metric.Fingerprint()
888+
if _, exists := outSet[fp]; exists {
889+
ev.errorf("duplicated label set in output of label_join(): %s", el.Metric.Metric)
890+
} else {
891+
outSet[fp] = struct{}{}
892+
}
893+
}
894+
return vector
895+
}
896+
852897
// === vector(s scalar) Vector ===
853898
func funcVector(ev *evaluator, args Expressions) model.Value {
854899
return vector{
@@ -986,25 +1031,25 @@ var functions = map[string]*Function{
9861031
Call: funcCountScalar,
9871032
},
9881033
"days_in_month": {
989-
Name: "days_in_month",
990-
ArgTypes: []model.ValueType{model.ValVector},
991-
OptionalArgs: 1,
992-
ReturnType: model.ValVector,
993-
Call: funcDaysInMonth,
1034+
Name: "days_in_month",
1035+
ArgTypes: []model.ValueType{model.ValVector},
1036+
Variadic: 1,
1037+
ReturnType: model.ValVector,
1038+
Call: funcDaysInMonth,
9941039
},
9951040
"day_of_month": {
996-
Name: "day_of_month",
997-
ArgTypes: []model.ValueType{model.ValVector},
998-
OptionalArgs: 1,
999-
ReturnType: model.ValVector,
1000-
Call: funcDayOfMonth,
1041+
Name: "day_of_month",
1042+
ArgTypes: []model.ValueType{model.ValVector},
1043+
Variadic: 1,
1044+
ReturnType: model.ValVector,
1045+
Call: funcDayOfMonth,
10011046
},
10021047
"day_of_week": {
1003-
Name: "day_of_week",
1004-
ArgTypes: []model.ValueType{model.ValVector},
1005-
OptionalArgs: 1,
1006-
ReturnType: model.ValVector,
1007-
Call: funcDayOfWeek,
1048+
Name: "day_of_week",
1049+
ArgTypes: []model.ValueType{model.ValVector},
1050+
Variadic: 1,
1051+
ReturnType: model.ValVector,
1052+
Call: funcDayOfWeek,
10081053
},
10091054
"delta": {
10101055
Name: "delta",
@@ -1049,11 +1094,11 @@ var functions = map[string]*Function{
10491094
Call: funcHoltWinters,
10501095
},
10511096
"hour": {
1052-
Name: "hour",
1053-
ArgTypes: []model.ValueType{model.ValVector},
1054-
OptionalArgs: 1,
1055-
ReturnType: model.ValVector,
1056-
Call: funcHour,
1097+
Name: "hour",
1098+
ArgTypes: []model.ValueType{model.ValVector},
1099+
Variadic: 1,
1100+
ReturnType: model.ValVector,
1101+
Call: funcHour,
10571102
},
10581103
"idelta": {
10591104
Name: "idelta",
@@ -1079,6 +1124,13 @@ var functions = map[string]*Function{
10791124
ReturnType: model.ValVector,
10801125
Call: funcLabelReplace,
10811126
},
1127+
"label_join": {
1128+
Name: "label_join",
1129+
ArgTypes: []model.ValueType{model.ValVector, model.ValString, model.ValString, model.ValString},
1130+
Variadic: -1,
1131+
ReturnType: model.ValVector,
1132+
Call: funcLabelJoin,
1133+
},
10821134
"ln": {
10831135
Name: "ln",
10841136
ArgTypes: []model.ValueType{model.ValVector},
@@ -1110,18 +1162,18 @@ var functions = map[string]*Function{
11101162
Call: funcMinOverTime,
11111163
},
11121164
"minute": {
1113-
Name: "minute",
1114-
ArgTypes: []model.ValueType{model.ValVector},
1115-
OptionalArgs: 1,
1116-
ReturnType: model.ValVector,
1117-
Call: funcMinute,
1165+
Name: "minute",
1166+
ArgTypes: []model.ValueType{model.ValVector},
1167+
Variadic: 1,
1168+
ReturnType: model.ValVector,
1169+
Call: funcMinute,
11181170
},
11191171
"month": {
1120-
Name: "month",
1121-
ArgTypes: []model.ValueType{model.ValVector},
1122-
OptionalArgs: 1,
1123-
ReturnType: model.ValVector,
1124-
Call: funcMonth,
1172+
Name: "month",
1173+
ArgTypes: []model.ValueType{model.ValVector},
1174+
Variadic: 1,
1175+
ReturnType: model.ValVector,
1176+
Call: funcMonth,
11251177
},
11261178
"predict_linear": {
11271179
Name: "predict_linear",
@@ -1148,11 +1200,11 @@ var functions = map[string]*Function{
11481200
Call: funcResets,
11491201
},
11501202
"round": {
1151-
Name: "round",
1152-
ArgTypes: []model.ValueType{model.ValVector, model.ValScalar},
1153-
OptionalArgs: 1,
1154-
ReturnType: model.ValVector,
1155-
Call: funcRound,
1203+
Name: "round",
1204+
ArgTypes: []model.ValueType{model.ValVector, model.ValScalar},
1205+
Variadic: 1,
1206+
ReturnType: model.ValVector,
1207+
Call: funcRound,
11561208
},
11571209
"scalar": {
11581210
Name: "scalar",
@@ -1209,11 +1261,11 @@ var functions = map[string]*Function{
12091261
Call: funcVector,
12101262
},
12111263
"year": {
1212-
Name: "year",
1213-
ArgTypes: []model.ValueType{model.ValVector},
1214-
OptionalArgs: 1,
1215-
ReturnType: model.ValVector,
1216-
Call: funcYear,
1264+
Name: "year",
1265+
ArgTypes: []model.ValueType{model.ValVector},
1266+
Variadic: 1,
1267+
ReturnType: model.ValVector,
1268+
Call: funcYear,
12171269
},
12181270
}
12191271

promql/parse.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -1086,13 +1086,23 @@ func (p *parser) checkType(node Node) (typ model.ValueType) {
10861086

10871087
case *Call:
10881088
nargs := len(n.Func.ArgTypes)
1089-
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) {
1090-
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
1091-
}
1092-
if nargs < len(n.Args) {
1093-
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
1089+
if n.Func.Variadic == 0 {
1090+
if nargs != len(n.Args) {
1091+
p.errorf("expected %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
1092+
}
1093+
} else {
1094+
na := nargs - 1
1095+
if na > len(n.Args) {
1096+
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
1097+
} else if nargsmax := na + n.Func.Variadic; n.Func.Variadic > 0 && nargsmax < len(n.Args) {
1098+
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargsmax, n.Func.Name, len(n.Args))
1099+
}
10941100
}
1101+
10951102
for i, arg := range n.Args {
1103+
if i >= len(n.Func.ArgTypes) {
1104+
i = len(n.Func.ArgTypes) - 1
1105+
}
10961106
p.expectType(arg, n.Func.ArgTypes[i], fmt.Sprintf("call to function %q", n.Func.Name))
10971107
}
10981108

promql/parse_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1356,11 +1356,11 @@ var testExpr = []struct {
13561356
}, {
13571357
input: "floor()",
13581358
fail: true,
1359-
errMsg: "expected at least 1 argument(s) in call to \"floor\", got 0",
1359+
errMsg: "expected 1 argument(s) in call to \"floor\", got 0",
13601360
}, {
13611361
input: "floor(some_metric, other_metric)",
13621362
fail: true,
1363-
errMsg: "expected at most 1 argument(s) in call to \"floor\", got 2",
1363+
errMsg: "expected 1 argument(s) in call to \"floor\", got 2",
13641364
}, {
13651365
input: "floor(1)",
13661366
fail: true,

promql/testdata/functions.test

+37
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,43 @@ eval_fail instant at 0m label_replace(testmetric, "src", "", "", "")
223223

224224
clear
225225

226+
# Tests for label_join.
227+
load 5m
228+
testmetric{src="a",src1="b",src2="c",dst="original-destination-value"} 0
229+
testmetric{src="d",src1="e",src2="f",dst="original-destination-value"} 1
230+
231+
# label_join joins all src values in order.
232+
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src1", "src2")
233+
testmetric{src="a",src1="b",src2="c",dst="a-b-c"} 0
234+
testmetric{src="d",src1="e",src2="f",dst="d-e-f"} 1
235+
236+
# label_join treats non existent src labels as empty strings.
237+
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src3", "src1")
238+
testmetric{src="a",src1="b",src2="c",dst="a--b"} 0
239+
testmetric{src="d",src1="e",src2="f",dst="d--e"} 1
240+
241+
# label_join overwrites the destination label even if the resulting dst label is empty string
242+
eval instant at 0m label_join(testmetric, "dst", "", "emptysrc", "emptysrc1", "emptysrc2")
243+
testmetric{src="a",src1="b",src2="c"} 0
244+
testmetric{src="d",src1="e",src2="f"} 1
245+
246+
# test without src label for label_join
247+
eval instant at 0m label_join(testmetric, "dst", ", ")
248+
testmetric{src="a",src1="b",src2="c"} 0
249+
testmetric{src="d",src1="e",src2="f"} 1
250+
251+
# test without dst label for label_join
252+
load 5m
253+
testmetric1{src="foo",src1="bar",src2="foobar"} 0
254+
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz"} 1
255+
256+
# label_join creates dst label if not present.
257+
eval instant at 0m label_join(testmetric1, "dst", ", ", "src", "src1", "src2")
258+
testmetric1{src="foo",src1="bar",src2="foobar",dst="foo, bar, foobar"} 0
259+
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz",dst="fizz, buzz, fizzbuzz"} 1
260+
261+
clear
262+
226263
# Tests for vector.
227264
eval instant at 0m vector(1)
228265
{} 1

0 commit comments

Comments
 (0)