diff --git a/app/vmselect/promql/exec_test.go b/app/vmselect/promql/exec_test.go index 1c73109cd..7978d9d72 100644 --- a/app/vmselect/promql/exec_test.go +++ b/app/vmselect/promql/exec_test.go @@ -1090,6 +1090,62 @@ func TestExecSuccess(t *testing.T) { resultExpected := []netstorage.Result{r1, r2, r3, r4, r5} f(q, resultExpected) }) + t.Run(`label_uppercase`, func(t *testing.T) { + t.Parallel() + q := `label_uppercase( + label_set(time(), "foo", "bAr", "XXx", "yyy", "zzz", "abc"), + "foo", "XXx", "aaa" + )` + r := netstorage.Result{ + MetricName: metricNameExpected, + Values: []float64{1000, 1200, 1400, 1600, 1800, 2000}, + Timestamps: timestampsExpected, + } + r.MetricName.Tags = []storage.Tag{ + { + Key: []byte("XXx"), + Value: []byte("YYY"), + }, + { + Key: []byte("foo"), + Value: []byte("BAR"), + }, + { + Key: []byte("zzz"), + Value: []byte("abc"), + }, + } + resultExpected := []netstorage.Result{r} + f(q, resultExpected) + }) + t.Run(`label_lowercase`, func(t *testing.T) { + t.Parallel() + q := `label_lowercase( + label_set(time(), "foo", "bAr", "XXx", "yyy", "zzz", "aBc"), + "foo", "XXx", "aaa" + )` + r := netstorage.Result{ + MetricName: metricNameExpected, + Values: []float64{1000, 1200, 1400, 1600, 1800, 2000}, + Timestamps: timestampsExpected, + } + r.MetricName.Tags = []storage.Tag{ + { + Key: []byte("XXx"), + Value: []byte("yyy"), + }, + { + Key: []byte("foo"), + Value: []byte("bar"), + }, + { + Key: []byte("zzz"), + Value: []byte("aBc"), + }, + } + resultExpected := []netstorage.Result{r} + f(q, resultExpected) + }) t.Run(`label_copy(new_tag)`, func(t *testing.T) { t.Parallel() q := `label_copy( @@ -6217,6 +6273,8 @@ func TestExecError(t *testing.T) { f(`label_transform(1, "foo", "invalid(regexp", "baz`) f(`label_match(1, 2, 3)`) f(`label_mismatch(1, 2, 3)`) + f(`label_uppercase()`) + f(`label_lowercase()`) f(`alias(1, 2)`) f(`aggr_over_time(1, 2)`) f(`aggr_over_time(("foo", "bar"), 3)`) diff --git a/app/vmselect/promql/transform.go b/app/vmselect/promql/transform.go index 8d350dc29..34ac4a6f7 100644 --- a/app/vmselect/promql/transform.go +++ b/app/vmselect/promql/transform.go @@ -73,6 +73,8 @@ var transformFuncs = map[string]transformFunc{ // New funcs "label_set": transformLabelSet, "label_map": transformLabelMap, + "label_uppercase": transformLabelUppercase, + "label_lowercase": transformLabelLowercase, "label_del": transformLabelDel, "label_keep": transformLabelKeep, "label_copy": transformLabelCopy, @@ -1196,6 +1198,42 @@ func transformLabelSet(tfa *transformFuncArg) ([]*timeseries, error) { return rvs, nil } +func transformLabelUppercase(tfa *transformFuncArg) ([]*timeseries, error) { + return transformLabelValueFunc(tfa, strings.ToUpper) +} + +func transformLabelLowercase(tfa *transformFuncArg) ([]*timeseries, error) { + return transformLabelValueFunc(tfa, strings.ToLower) +} + +func transformLabelValueFunc(tfa *transformFuncArg, f func(string) string) ([]*timeseries, error) { + args := tfa.args + if len(args) < 2 { + return nil, fmt.Errorf(`not enough args; got %d; want at least %d`, len(args), 2) + } + labels := make([]string, 0, len(args)-1) + for i := 1; i < len(args); i++ { + label, err := getString(args[i], i) + if err != nil { + return nil, err + } + labels = append(labels, label) + } + + rvs := args[0] + for _, ts := range rvs { + mn := &ts.MetricName + for _, label := range labels { + dstValue := getDstValue(mn, label) + *dstValue = append((*dstValue)[:0], f(string(*dstValue))...) + if len(*dstValue) == 0 { + mn.RemoveTag(label) + } + } + } + return rvs, nil +} + func transformLabelMap(tfa *transformFuncArg) ([]*timeseries, error) { args := tfa.args if len(args) < 2 { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a09a3de68..2c9fc06f3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,8 @@ # tip * FEATURE: optimize Consul service discovery speed when discovering big number of services. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/574 +* FEATURE: add `label_uppercase(q, label1, ... labelN)` and `label_lowercase(q, label1, ... labelN)` function to [MetricsQL](https://victoriametrics.github.io/MetricsQL.html) + for uppercasing and lowercasing values for the given labels. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/936 * BUGFIX: properly parse timestamps in OpenMetrics format - they are exposed as floating-point number in seconds instead of integer milliseconds unlike in Prometheus exposition format. See [the docs](https://github.com/OpenObservability/OpenMetrics/blob/master/specification/OpenMetrics.md#timestamps). diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md index f424f16b7..9fdd5d983 100644 --- a/docs/MetricsQL.md +++ b/docs/MetricsQL.md @@ -57,6 +57,8 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g - `alias(q, name)` for setting metric name across all the time series `q`. - `label_set(q, label1, value1, ... labelN, valueN)` for setting the given values for the given labels on `q`. - `label_map(q, label, srcValue1, dstValue1, ... srcValueN, dstValueN)` for mapping `label` values from `src*` to `dst*`. + - `label_uppercase(q, label1, ... labelN)` for uppercasing values for the given labels. + - `label_lowercase(q, label2, ... labelN)` for lowercasing value for the given labels. - `label_del(q, label1, ... labelN)` for deleting the given labels from `q`. - `label_keep(q, label1, ... labelN)` for deleting all the labels except the given labels from `q`. - `label_copy(q, src_label1, dst_label1, ... src_labelN, dst_labelN)` for copying label values from `src_*` to `dst_*`. diff --git a/go.mod b/go.mod index 2d5147b09..34451eb8d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( // like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b github.com/VictoriaMetrics/fasthttp v1.0.9 github.com/VictoriaMetrics/metrics v1.12.3 - github.com/VictoriaMetrics/metricsql v0.7.3 + github.com/VictoriaMetrics/metricsql v0.8.0 github.com/aws/aws-sdk-go v1.36.0 github.com/cespare/xxhash/v2 v2.1.1 github.com/golang/snappy v0.0.2 diff --git a/go.sum b/go.sum index cf9ae2416..219c369bd 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/VictoriaMetrics/fasthttp v1.0.9/go.mod h1:3SeUL4zwB/p/a9aEeRc6gdlbrtN github.com/VictoriaMetrics/metrics v1.12.2/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE= github.com/VictoriaMetrics/metrics v1.12.3 h1:Fe6JHC6MSEKa+BtLhPN8WIvS+HKPzMc2evEpNeCGy7I= github.com/VictoriaMetrics/metrics v1.12.3/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE= -github.com/VictoriaMetrics/metricsql v0.7.3 h1:ReI+UBleCkGMmYDt69gsuGld71l1WpQDIlhgU1N5xQ8= -github.com/VictoriaMetrics/metricsql v0.7.3/go.mod h1:ylO7YITho/Iw6P71oEaGyHbO94bGoGtzWfLGqFhMIg8= +github.com/VictoriaMetrics/metricsql v0.8.0 h1:fUZWNjXYJhiCKoEXr86pM4RaAxfA3SqkAhJLe1gr/mo= +github.com/VictoriaMetrics/metricsql v0.8.0/go.mod h1:ylO7YITho/Iw6P71oEaGyHbO94bGoGtzWfLGqFhMIg8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= diff --git a/vendor/github.com/VictoriaMetrics/metricsql/optimizer.go b/vendor/github.com/VictoriaMetrics/metricsql/optimizer.go index b337b22dd..4f559f066 100644 --- a/vendor/github.com/VictoriaMetrics/metricsql/optimizer.go +++ b/vendor/github.com/VictoriaMetrics/metricsql/optimizer.go @@ -110,7 +110,7 @@ func getMetricExprForOptimization(e Expr) *MetricExpr { if IsTransformFunc(fe.Name) { switch strings.ToLower(fe.Name) { case "absent", "histogram_quantile", "label_join", "label_replace", "scalar", "vector", - "label_set", "label_map", "label_del", "label_keep", "label_copy", + "label_set", "label_map", "label_uppercase", "label_lowercase", "label_del", "label_keep", "label_copy", "label_move", "label_transform", "label_value", "label_match", "label_mismatch", "prometheus_buckets", "buckets_limit", "histogram_share", "union", "": // metric expressions for these functions cannot be optimized. diff --git a/vendor/github.com/VictoriaMetrics/metricsql/transform.go b/vendor/github.com/VictoriaMetrics/metricsql/transform.go index cd83a903b..c1196badf 100644 --- a/vendor/github.com/VictoriaMetrics/metricsql/transform.go +++ b/vendor/github.com/VictoriaMetrics/metricsql/transform.go @@ -39,6 +39,8 @@ var transformFuncs = map[string]bool{ // New funcs from MetricsQL "label_set": true, "label_map": true, + "label_uppercase": true, + "label_lowercase": true, "label_del": true, "label_keep": true, "label_copy": true, diff --git a/vendor/modules.txt b/vendor/modules.txt index 36c1d6683..e8753f365 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -16,7 +16,7 @@ github.com/VictoriaMetrics/fasthttp/fasthttputil github.com/VictoriaMetrics/fasthttp/stackless # github.com/VictoriaMetrics/metrics v1.12.3 github.com/VictoriaMetrics/metrics -# github.com/VictoriaMetrics/metricsql v0.7.3 +# github.com/VictoriaMetrics/metricsql v0.8.0 github.com/VictoriaMetrics/metricsql github.com/VictoriaMetrics/metricsql/binaryop # github.com/aws/aws-sdk-go v1.36.0