Add sort() and sort_desc() expression language functions.
This commit is contained in:
parent
adeabca230
commit
6cb3c51d24
|
@ -17,6 +17,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/prometheus/prometheus/model"
|
"github.com/prometheus/prometheus/model"
|
||||||
|
"github.com/prometheus/prometheus/utility"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,17 +64,17 @@ func (function *Function) CheckArgTypes(args []Node) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// === time() ===
|
// === time() model.SampleValue ===
|
||||||
func timeImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
func timeImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
return model.SampleValue(time.Now().Unix())
|
return model.SampleValue(time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
// === count(vector VectorNode) ===
|
// === count(vector VectorNode) model.SampleValue ===
|
||||||
func countImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
func countImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
return model.SampleValue(len(args[0].(VectorNode).Eval(timestamp, view)))
|
return model.SampleValue(len(args[0].(VectorNode).Eval(timestamp, view)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// === delta(matrix MatrixNode, isCounter ScalarNode) ===
|
// === delta(matrix MatrixNode, isCounter ScalarNode) Vector ===
|
||||||
func deltaImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
func deltaImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
matrixNode := args[0].(MatrixNode)
|
matrixNode := args[0].(MatrixNode)
|
||||||
isCounter := int(args[1].(ScalarNode).Eval(timestamp, view))
|
isCounter := int(args[1].(ScalarNode).Eval(timestamp, view))
|
||||||
|
@ -108,7 +110,7 @@ func deltaImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{}
|
||||||
return resultVector
|
return resultVector
|
||||||
}
|
}
|
||||||
|
|
||||||
// === rate(node *MatrixNode) ===
|
// === rate(node *MatrixNode) Vector ===
|
||||||
func rateImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
func rateImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
args = append(args, &ScalarLiteral{value: 1})
|
args = append(args, &ScalarLiteral{value: 1})
|
||||||
vector := deltaImpl(timestamp, view, args).(Vector)
|
vector := deltaImpl(timestamp, view, args).(Vector)
|
||||||
|
@ -123,7 +125,43 @@ func rateImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
return vector
|
return vector
|
||||||
}
|
}
|
||||||
|
|
||||||
// === sampleVectorImpl() ===
|
type vectorByValueSorter struct {
|
||||||
|
vector Vector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter vectorByValueSorter) Len() int {
|
||||||
|
return len(sorter.vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter vectorByValueSorter) Less(i, j int) (less bool) {
|
||||||
|
return sorter.vector[i].Value < sorter.vector[j].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter vectorByValueSorter) Swap(i, j int) {
|
||||||
|
sorter.vector[i], sorter.vector[j] = sorter.vector[j], sorter.vector[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// === sort(node *VectorNode) Vector ===
|
||||||
|
func sortImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
|
byValueSorter := vectorByValueSorter{
|
||||||
|
vector: args[0].(VectorNode).Eval(timestamp, view),
|
||||||
|
}
|
||||||
|
sort.Sort(byValueSorter)
|
||||||
|
return byValueSorter.vector
|
||||||
|
}
|
||||||
|
|
||||||
|
// === sortDesc(node *VectorNode) Vector ===
|
||||||
|
func sortDescImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
|
descByValueSorter := utility.ReverseSorter{
|
||||||
|
vectorByValueSorter{
|
||||||
|
vector: args[0].(VectorNode).Eval(timestamp, view),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sort.Sort(descByValueSorter)
|
||||||
|
return descByValueSorter.Interface.(vectorByValueSorter).vector
|
||||||
|
}
|
||||||
|
|
||||||
|
// === sampleVectorImpl() Vector ===
|
||||||
func sampleVectorImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
func sampleVectorImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
|
||||||
return Vector{
|
return Vector{
|
||||||
model.Sample{
|
model.Sample{
|
||||||
|
@ -197,12 +235,6 @@ func sampleVectorImpl(timestamp time.Time, view *viewAdapter, args []Node) inter
|
||||||
}
|
}
|
||||||
|
|
||||||
var functions = map[string]*Function{
|
var functions = map[string]*Function{
|
||||||
"time": {
|
|
||||||
name: "time",
|
|
||||||
argTypes: []ExprType{},
|
|
||||||
returnType: SCALAR,
|
|
||||||
callFn: timeImpl,
|
|
||||||
},
|
|
||||||
"count": {
|
"count": {
|
||||||
name: "count",
|
name: "count",
|
||||||
argTypes: []ExprType{VECTOR},
|
argTypes: []ExprType{VECTOR},
|
||||||
|
@ -227,6 +259,24 @@ var functions = map[string]*Function{
|
||||||
returnType: VECTOR,
|
returnType: VECTOR,
|
||||||
callFn: sampleVectorImpl,
|
callFn: sampleVectorImpl,
|
||||||
},
|
},
|
||||||
|
"sort": {
|
||||||
|
name: "sort",
|
||||||
|
argTypes: []ExprType{VECTOR},
|
||||||
|
returnType: VECTOR,
|
||||||
|
callFn: sortImpl,
|
||||||
|
},
|
||||||
|
"sort_desc": {
|
||||||
|
name: "sort_desc",
|
||||||
|
argTypes: []ExprType{VECTOR},
|
||||||
|
returnType: VECTOR,
|
||||||
|
callFn: sortDescImpl,
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
name: "time",
|
||||||
|
argTypes: []ExprType{},
|
||||||
|
returnType: SCALAR,
|
||||||
|
callFn: timeImpl,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFunction(name string) (*Function, error) {
|
func GetFunction(name string) (*Function, error) {
|
||||||
|
|
|
@ -88,7 +88,6 @@ func (vector Vector) String() string {
|
||||||
strings.Join(labelStrings, ","),
|
strings.Join(labelStrings, ","),
|
||||||
sample.Value, sample.Timestamp))
|
sample.Value, sample.Timestamp))
|
||||||
}
|
}
|
||||||
sort.Strings(metricStrings)
|
|
||||||
return strings.Join(metricStrings, "\n")
|
return strings.Join(metricStrings, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,12 @@ import (
|
||||||
|
|
||||||
var testEvalTime = testStartTime.Add(testDuration5m * 10)
|
var testEvalTime = testStartTime.Add(testDuration5m * 10)
|
||||||
|
|
||||||
// Expected output needs to be alphabetically sorted (labels within one line
|
// Labels in expected output need to be alphabetically sorted.
|
||||||
// must be sorted and lines between each other must be sorted too).
|
|
||||||
var expressionTests = []struct {
|
var expressionTests = []struct {
|
||||||
expr string
|
expr string
|
||||||
output []string
|
output []string
|
||||||
shouldFail bool
|
shouldFail bool
|
||||||
|
checkOrder bool
|
||||||
fullRanges int
|
fullRanges int
|
||||||
intervalRanges int
|
intervalRanges int
|
||||||
}{
|
}{
|
||||||
|
@ -179,6 +179,36 @@ var expressionTests = []struct {
|
||||||
},
|
},
|
||||||
fullRanges: 8,
|
fullRanges: 8,
|
||||||
intervalRanges: 0,
|
intervalRanges: 0,
|
||||||
|
}, {
|
||||||
|
expr: "sort(http_requests)",
|
||||||
|
output: []string{
|
||||||
|
"http_requests{group='production',instance='0',job='api-server'} => 100 @[%v]",
|
||||||
|
"http_requests{group='production',instance='1',job='api-server'} => 200 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='0',job='api-server'} => 300 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='1',job='api-server'} => 400 @[%v]",
|
||||||
|
"http_requests{group='production',instance='0',job='app-server'} => 500 @[%v]",
|
||||||
|
"http_requests{group='production',instance='1',job='app-server'} => 600 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='0',job='app-server'} => 700 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='1',job='app-server'} => 800 @[%v]",
|
||||||
|
},
|
||||||
|
checkOrder: true,
|
||||||
|
fullRanges: 0,
|
||||||
|
intervalRanges: 8,
|
||||||
|
}, {
|
||||||
|
expr: "sort_desc(http_requests)",
|
||||||
|
output: []string{
|
||||||
|
"http_requests{group='canary',instance='1',job='app-server'} => 800 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='0',job='app-server'} => 700 @[%v]",
|
||||||
|
"http_requests{group='production',instance='1',job='app-server'} => 600 @[%v]",
|
||||||
|
"http_requests{group='production',instance='0',job='app-server'} => 500 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='1',job='api-server'} => 400 @[%v]",
|
||||||
|
"http_requests{group='canary',instance='0',job='api-server'} => 300 @[%v]",
|
||||||
|
"http_requests{group='production',instance='1',job='api-server'} => 200 @[%v]",
|
||||||
|
"http_requests{group='production',instance='0',job='api-server'} => 100 @[%v]",
|
||||||
|
},
|
||||||
|
checkOrder: true,
|
||||||
|
fullRanges: 0,
|
||||||
|
intervalRanges: 8,
|
||||||
}, {
|
}, {
|
||||||
expr: "x{y='testvalue'}",
|
expr: "x{y='testvalue'}",
|
||||||
output: []string{
|
output: []string{
|
||||||
|
@ -232,7 +262,7 @@ func TestExpressions(t *testing.T) {
|
||||||
storeMatrix(tieredStorage, testMatrix)
|
storeMatrix(tieredStorage, testMatrix)
|
||||||
tieredStorage.Flush()
|
tieredStorage.Flush()
|
||||||
|
|
||||||
for _, exprTest := range expressionTests {
|
for i, exprTest := range expressionTests {
|
||||||
expectedLines := annotateWithTime(exprTest.output)
|
expectedLines := annotateWithTime(exprTest.output)
|
||||||
|
|
||||||
testExpr, err := LoadExprFromString(exprTest.expr)
|
testExpr, err := LoadExprFromString(exprTest.expr)
|
||||||
|
@ -241,21 +271,30 @@ func TestExpressions(t *testing.T) {
|
||||||
if exprTest.shouldFail {
|
if exprTest.shouldFail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Errorf("Error during parsing: %v", err)
|
t.Errorf("%d Error during parsing: %v", i, err)
|
||||||
t.Errorf("Expression: %v", exprTest.expr)
|
t.Errorf("%d Expression: %v", i, exprTest.expr)
|
||||||
} else {
|
} else {
|
||||||
if exprTest.shouldFail {
|
if exprTest.shouldFail {
|
||||||
t.Errorf("Test should fail, but didn't")
|
t.Errorf("%d Test should fail, but didn't", i)
|
||||||
}
|
}
|
||||||
failed := false
|
failed := false
|
||||||
resultStr := ast.EvalToString(testExpr, testEvalTime, ast.TEXT)
|
resultStr := ast.EvalToString(testExpr, testEvalTime, ast.TEXT)
|
||||||
resultLines := strings.Split(resultStr, "\n")
|
resultLines := strings.Split(resultStr, "\n")
|
||||||
|
|
||||||
if len(exprTest.output) != len(resultLines) {
|
if len(exprTest.output) != len(resultLines) {
|
||||||
t.Errorf("Number of samples in expected and actual output don't match")
|
t.Errorf("%d Number of samples in expected and actual output don't match", i)
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
for _, expectedSample := range expectedLines {
|
|
||||||
|
if exprTest.checkOrder {
|
||||||
|
for j, expectedSample := range expectedLines {
|
||||||
|
if resultLines[j] != expectedSample {
|
||||||
|
t.Errorf("%d.%d Expected sample '%v', got '%v'", i, j, resultLines[j], expectedSample)
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for j, expectedSample := range expectedLines {
|
||||||
found := false
|
found := false
|
||||||
for _, actualSample := range resultLines {
|
for _, actualSample := range resultLines {
|
||||||
if actualSample == expectedSample {
|
if actualSample == expectedSample {
|
||||||
|
@ -263,25 +302,25 @@ func TestExpressions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("Couldn't find expected sample in output: '%v'",
|
t.Errorf("%d.%d Couldn't find expected sample in output: '%v'", i, j, expectedSample)
|
||||||
expectedSample)
|
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
analyzer := ast.NewQueryAnalyzer()
|
analyzer := ast.NewQueryAnalyzer()
|
||||||
analyzer.AnalyzeQueries(testExpr)
|
analyzer.AnalyzeQueries(testExpr)
|
||||||
if exprTest.fullRanges != len(analyzer.FullRanges) {
|
if exprTest.fullRanges != len(analyzer.FullRanges) {
|
||||||
t.Errorf("Count of full ranges didn't match: %v vs %v", exprTest.fullRanges, len(analyzer.FullRanges))
|
t.Errorf("%d Count of full ranges didn't match: %v vs %v", i, exprTest.fullRanges, len(analyzer.FullRanges))
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
if exprTest.intervalRanges != len(analyzer.IntervalRanges) {
|
if exprTest.intervalRanges != len(analyzer.IntervalRanges) {
|
||||||
t.Errorf("Count of stepped ranges didn't match: %v vs %v", exprTest.intervalRanges, len(analyzer.IntervalRanges))
|
t.Errorf("%d Count of interval ranges didn't match: %v vs %v", i, exprTest.intervalRanges, len(analyzer.IntervalRanges))
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if failed {
|
||||||
t.Errorf("Expression: %v\n%v", exprTest.expr, vectorComparisonString(expectedLines, resultLines))
|
t.Errorf("%d Expression: %v\n%v", i, exprTest.expr, vectorComparisonString(expectedLines, resultLines))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2013 Prometheus Team
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utility
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReverseSorter embeds a sort.Interface value and implements a reverse sort
|
||||||
|
// over that value.
|
||||||
|
type ReverseSorter struct {
|
||||||
|
// This embedded interface permits ReverseSorter to use the methods of
|
||||||
|
// another Interface implementation.
|
||||||
|
sort.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns the opposite of the embedded implementation's Less method.
|
||||||
|
func (r ReverseSorter) Less(i, j int) bool {
|
||||||
|
return r.Interface.Less(j, i)
|
||||||
|
}
|
Loading…
Reference in New Issue