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
}
switch fe.Name {
case "sort", "sort_desc":
case "sort", "sort_desc",
"sort_by_label", "sort_by_label_desc":
return false
default:
return true

View file

@ -1597,6 +1597,48 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r1, r2}
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.Parallel()
q := `sort_desc((
@ -5338,6 +5380,8 @@ func TestExecError(t *testing.T) {
f(`scalar()`)
f(`sort(1,2)`)
f(`sort_desc()`)
f(`sort_by_label()`)
f(`sort_by_label_desc()`)
f(`timestamp()`)
f(`vector()`)
f(`histogram_quantile()`)

View file

@ -97,6 +97,8 @@ var transformFuncs = map[string]transformFunc{
"acos": newTransformFuncOneArg(transformAcos),
"prometheus_buckets": transformPrometheusBuckets,
"histogram_share": transformHistogramShare,
"sort_by_label": newTransformFuncSortByLabel(false),
"sort_by_label_desc": newTransformFuncSortByLabel(true),
}
func getTransformFunc(s string) transformFunc {
@ -1355,6 +1357,29 @@ func transformScalar(tfa *transformFuncArg) ([]*timeseries, error) {
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 {
return func(tfa *transformFuncArg) ([]*timeseries, error) {
args := tfa.args
@ -1367,7 +1392,7 @@ func newTransformFuncSort(isDesc bool) transformFunc {
b := rvs[j].Values
n := len(a) - 1
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
}
n--
@ -1375,11 +1400,10 @@ func newTransformFuncSort(isDesc bool) transformFunc {
if n < 0 {
return false
}
cmp := a[n] < b[n]
if isDesc {
cmp = !cmp
return b[n] < a[n]
}
return cmp
return a[n] < b[n]
})
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_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.
- `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.
- `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`.

View file

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