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
|
||||
}
|
||||
switch fe.Name {
|
||||
case "sort", "sort_desc":
|
||||
case "sort", "sort_desc",
|
||||
"sort_by_label", "sort_by_label_desc":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
|
|
@ -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()`)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue