app/vmselect: add sort_by_label(q, label) and sort_by_label_desc(q, label) functions

This is implementation of https://github.com/prometheus/prometheus/pull/1533 for VictoriaMetrics.
This commit is contained in:
Aliaksandr Valialkin 2020-02-13 17:00:47 +02:00
parent 7836ad8907
commit 7b1c7051a3
5 changed files with 77 additions and 5 deletions

View file

@ -71,7 +71,8 @@ func maySortResults(e metricsql.Expr, tss []*timeseries) bool {
return true return true
} }
switch fe.Name { switch fe.Name {
case "sort", "sort_desc": case "sort", "sort_desc",
"sort_by_label", "sort_by_label_desc":
return false return false
default: default:
return true return true

View file

@ -1597,6 +1597,48 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r1, r2} resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected) f(q, resultExpected)
}) })
t.Run(`sort_by_label()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
alias(1, "foo"),
alias(2, "bar"),
), "__name__")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{2, 2, 2, 2, 2, 2},
Timestamps: timestampsExpected,
}
r1.MetricName.MetricGroup = []byte("bar")
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1, 1, 1, 1, 1, 1},
Timestamps: timestampsExpected,
}
r2.MetricName.MetricGroup = []byte("foo")
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`sort_by_label_desc()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label_desc((
alias(1, "foo"),
alias(2, "bar"),
), "__name__")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1, 1, 1, 1, 1, 1},
Timestamps: timestampsExpected,
}
r1.MetricName.MetricGroup = []byte("foo")
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{2, 2, 2, 2, 2, 2},
Timestamps: timestampsExpected,
}
r2.MetricName.MetricGroup = []byte("bar")
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`a cmp scalar (leave MetricGroup)`, func(t *testing.T) { t.Run(`a cmp scalar (leave MetricGroup)`, func(t *testing.T) {
t.Parallel() t.Parallel()
q := `sort_desc(( q := `sort_desc((
@ -5338,6 +5380,8 @@ func TestExecError(t *testing.T) {
f(`scalar()`) f(`scalar()`)
f(`sort(1,2)`) f(`sort(1,2)`)
f(`sort_desc()`) f(`sort_desc()`)
f(`sort_by_label()`)
f(`sort_by_label_desc()`)
f(`timestamp()`) f(`timestamp()`)
f(`vector()`) f(`vector()`)
f(`histogram_quantile()`) f(`histogram_quantile()`)

View file

@ -97,6 +97,8 @@ var transformFuncs = map[string]transformFunc{
"acos": newTransformFuncOneArg(transformAcos), "acos": newTransformFuncOneArg(transformAcos),
"prometheus_buckets": transformPrometheusBuckets, "prometheus_buckets": transformPrometheusBuckets,
"histogram_share": transformHistogramShare, "histogram_share": transformHistogramShare,
"sort_by_label": newTransformFuncSortByLabel(false),
"sort_by_label_desc": newTransformFuncSortByLabel(true),
} }
func getTransformFunc(s string) transformFunc { func getTransformFunc(s string) transformFunc {
@ -1355,6 +1357,29 @@ func transformScalar(tfa *transformFuncArg) ([]*timeseries, error) {
return arg, nil return arg, nil
} }
func newTransformFuncSortByLabel(isDesc bool) transformFunc {
return func(tfa *transformFuncArg) ([]*timeseries, error) {
args := tfa.args
if err := expectTransformArgsNum(args, 2); err != nil {
return nil, err
}
label, err := getString(args[1], 1)
if err != nil {
return nil, fmt.Errorf("cannot parse label name for sorting: %s", err)
}
rvs := args[0]
sort.SliceStable(rvs, func(i, j int) bool {
a := rvs[i].MetricName.GetTagValue(label)
b := rvs[j].MetricName.GetTagValue(label)
if isDesc {
return string(b) < string(a)
}
return string(a) < string(b)
})
return rvs, nil
}
}
func newTransformFuncSort(isDesc bool) transformFunc { func newTransformFuncSort(isDesc bool) transformFunc {
return func(tfa *transformFuncArg) ([]*timeseries, error) { return func(tfa *transformFuncArg) ([]*timeseries, error) {
args := tfa.args args := tfa.args
@ -1367,7 +1392,7 @@ func newTransformFuncSort(isDesc bool) transformFunc {
b := rvs[j].Values b := rvs[j].Values
n := len(a) - 1 n := len(a) - 1
for n >= 0 { for n >= 0 {
if !math.IsNaN(a[n]) && !math.IsNaN(b[n]) { if !math.IsNaN(a[n]) && !math.IsNaN(b[n]) && a[n] != b[n] {
break break
} }
n-- n--
@ -1375,11 +1400,10 @@ func newTransformFuncSort(isDesc bool) transformFunc {
if n < 0 { if n < 0 {
return false return false
} }
cmp := a[n] < b[n]
if isDesc { if isDesc {
cmp = !cmp return b[n] < a[n]
} }
return cmp return a[n] < b[n]
}) })
return rvs, nil return rvs, nil
} }

View file

@ -53,6 +53,7 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g
- `label_transform(q, label, regexp, replacement)` for replacing all the `regexp` occurences with `replacement` in the `label` values from `q`. - `label_transform(q, label, regexp, replacement)` for replacing all the `regexp` occurences with `replacement` in the `label` values from `q`.
- `label_value(q, label)` - returns numeric values for the given `label` from `q`. - `label_value(q, label)` - returns numeric values for the given `label` from `q`.
- `label_match(q, label, regexp)` and `label_mismatch(q, label, regexp)` for filtering time series with labels matching (or not matching) the given regexps. - `label_match(q, label, regexp)` and `label_mismatch(q, label, regexp)` for filtering time series with labels matching (or not matching) the given regexps.
- `sort_by_label(q, label)` and `sort_by_label_desc(q, label)` for sorting time series by the given `label`.
- `step()` function for returning the step in seconds used in the query. - `step()` function for returning the step in seconds used in the query.
- `start()` and `end()` functions for returning the start and end timestamps of the `[start ... end]` range used in the query. - `start()` and `end()` functions for returning the start and end timestamps of the `[start ... end]` range used in the query.
- `integrate(m[d])` for returning integral over the given duration `d` for the given metric `m`. - `integrate(m[d])` for returning integral over the given duration `d` for the given metric `m`.

View file

@ -76,6 +76,8 @@ var transformFuncs = map[string]bool{
"acos": true, "acos": true,
"prometheus_buckets": true, "prometheus_buckets": true,
"histogram_share": true, "histogram_share": true,
"sort_by_label": true,
"sort_by_label_desc": true,
} }
// IsTransformFunc returns whether funcName is known transform function. // IsTransformFunc returns whether funcName is known transform function.