mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
7836ad8907
commit
7b1c7051a3
5 changed files with 77 additions and 5 deletions
|
@ -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
|
||||||
|
|
|
@ -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()`)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue