Skip to content

Commit

Permalink
promql: use natural sort in sort_by_label and sort_by_label_desc (pro…
Browse files Browse the repository at this point in the history
…metheus#13411)

These functions are intended for humans, as robots can already sort the results
however they please. Humans like things sorted "naturally":

* https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/

A similar thing has been done to Grafana, which is also used by humans:

* grafana/grafana#78024
* grafana/grafana#78494

Signed-off-by: Ivan Babrou <[email protected]>
  • Loading branch information
bobrik authored Jan 17, 2024
1 parent 7153f61 commit a6b35ff
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 15 deletions.
4 changes: 4 additions & 0 deletions docs/querying/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ Same as `sort`, but sorts in descending order.

Please note that the sort by label functions only affect the results of instant queries, as range query results always have a fixed output ordering.

This function uses [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).

## `sort_by_label_desc()`

**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
Expand All @@ -602,6 +604,8 @@ Same as `sort_by_label`, but sorts in descending order.

Please note that the sort by label functions only affect the results of instant queries, as range query results always have a fixed output ordering.

This function uses [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).

## `sqrt()`

`sqrt(v instant-vector)` calculates the square root of all elements in `v`.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/edsrzf/mmap-go v1.1.0
github.com/envoyproxy/go-control-plane v0.11.1
github.com/envoyproxy/protoc-gen-validate v1.0.2
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
github.com/fsnotify/fsnotify v1.7.0
github.com/go-kit/log v0.2.1
github.com/go-logfmt/logfmt v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
Expand Down
29 changes: 14 additions & 15 deletions promql/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"
"time"

"github.com/facette/natsort"
"github.com/grafana/regexp"
"github.com/prometheus/common/model"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -380,15 +381,16 @@ func funcSortByLabel(vals []parser.Value, args parser.Expressions, enh *EvalNode
for _, label := range labels {
lv1 := a.Metric.Get(label)
lv2 := b.Metric.Get(label)
// 0 if a == b, -1 if a < b, and +1 if a > b.
switch strings.Compare(lv1, lv2) {
case -1:
return -1
case +1:
return +1
default:

if lv1 == lv2 {
continue
}

if natsort.Compare(lv1, lv2) {
return -1
}

return +1
}

return 0
Expand All @@ -409,19 +411,16 @@ func funcSortByLabelDesc(vals []parser.Value, args parser.Expressions, enh *Eval
for _, label := range labels {
lv1 := a.Metric.Get(label)
lv2 := b.Metric.Get(label)
// If label values are the same, continue to the next label

if lv1 == lv2 {
continue
}
// 0 if a == b, -1 if a < b, and +1 if a > b.
switch strings.Compare(lv1, lv2) {
case -1:

if natsort.Compare(lv1, lv2) {
return +1
case +1:
return -1
default:
continue
}

return -1
}

return 0
Expand Down
35 changes: 35 additions & 0 deletions promql/testdata/functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,19 @@ load 5m
http_requests{job="app-server", instance="0", group="canary"} 0+70x10
http_requests{job="app-server", instance="1", group="canary"} 0+80x10
http_requests{job="api-server", instance="2", group="production"} 0+10x10
cpu_time_total{job="cpu", cpu="0"} 0+10x10
cpu_time_total{job="cpu", cpu="1"} 0+10x10
cpu_time_total{job="cpu", cpu="2"} 0+10x10
cpu_time_total{job="cpu", cpu="3"} 0+10x10
cpu_time_total{job="cpu", cpu="10"} 0+10x10
cpu_time_total{job="cpu", cpu="11"} 0+10x10
cpu_time_total{job="cpu", cpu="12"} 0+10x10
cpu_time_total{job="cpu", cpu="20"} 0+10x10
cpu_time_total{job="cpu", cpu="21"} 0+10x10
cpu_time_total{job="cpu", cpu="100"} 0+10x10
node_uname_info{job="node_exporter", instance="4m600", release="1.2.3"} 0+10x10
node_uname_info{job="node_exporter", instance="4m5", release="1.11.3"} 0+10x10
node_uname_info{job="node_exporter", instance="4m1000", release="1.111.3"} 0+10x10

eval_ordered instant at 50m sort_by_label(http_requests, "instance")
http_requests{group="production", instance="0", job="api-server"} 100
Expand Down Expand Up @@ -579,6 +592,28 @@ eval_ordered instant at 50m sort_by_label_desc(http_requests, "instance", "group
http_requests{group="canary", instance="0", job="app-server"} 700
http_requests{group="canary", instance="0", job="api-server"} 300

eval_ordered instant at 50m sort_by_label(cpu_time_total, "cpu")
cpu_time_total{job="cpu", cpu="0"} 100
cpu_time_total{job="cpu", cpu="1"} 100
cpu_time_total{job="cpu", cpu="2"} 100
cpu_time_total{job="cpu", cpu="3"} 100
cpu_time_total{job="cpu", cpu="10"} 100
cpu_time_total{job="cpu", cpu="11"} 100
cpu_time_total{job="cpu", cpu="12"} 100
cpu_time_total{job="cpu", cpu="20"} 100
cpu_time_total{job="cpu", cpu="21"} 100
cpu_time_total{job="cpu", cpu="100"} 100

eval_ordered instant at 50m sort_by_label(node_uname_info, "instance")
node_uname_info{job="node_exporter", instance="4m5", release="1.11.3"} 100
node_uname_info{job="node_exporter", instance="4m600", release="1.2.3"} 100
node_uname_info{job="node_exporter", instance="4m1000", release="1.111.3"} 100

eval_ordered instant at 50m sort_by_label(node_uname_info, "release")
node_uname_info{job="node_exporter", instance="4m600", release="1.2.3"} 100
node_uname_info{job="node_exporter", instance="4m5", release="1.11.3"} 100
node_uname_info{job="node_exporter", instance="4m1000", release="1.111.3"} 100

# Tests for holt_winters
clear

Expand Down

0 comments on commit a6b35ff

Please sign in to comment.