Add sort() and sort_desc() expression language functions.

This commit is contained in:
Julius Volz 2013-04-10 10:44:13 +02:00
parent adeabca230
commit 6cb3c51d24
4 changed files with 150 additions and 31 deletions

View file

@ -17,6 +17,8 @@ import (
"errors"
"fmt"
"github.com/prometheus/prometheus/model"
"github.com/prometheus/prometheus/utility"
"sort"
"time"
)
@ -62,17 +64,17 @@ func (function *Function) CheckArgTypes(args []Node) error {
return nil
}
// === time() ===
// === time() model.SampleValue ===
func timeImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
return model.SampleValue(time.Now().Unix())
}
// === count(vector VectorNode) ===
// === count(vector VectorNode) model.SampleValue ===
func countImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
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{} {
matrixNode := args[0].(MatrixNode)
isCounter := int(args[1].(ScalarNode).Eval(timestamp, view))
@ -108,7 +110,7 @@ func deltaImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{}
return resultVector
}
// === rate(node *MatrixNode) ===
// === rate(node *MatrixNode) Vector ===
func rateImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
args = append(args, &ScalarLiteral{value: 1})
vector := deltaImpl(timestamp, view, args).(Vector)
@ -123,7 +125,43 @@ func rateImpl(timestamp time.Time, view *viewAdapter, args []Node) interface{} {
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{} {
return Vector{
model.Sample{
@ -197,12 +235,6 @@ func sampleVectorImpl(timestamp time.Time, view *viewAdapter, args []Node) inter
}
var functions = map[string]*Function{
"time": {
name: "time",
argTypes: []ExprType{},
returnType: SCALAR,
callFn: timeImpl,
},
"count": {
name: "count",
argTypes: []ExprType{VECTOR},
@ -227,6 +259,24 @@ var functions = map[string]*Function{
returnType: VECTOR,
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) {

View file

@ -88,7 +88,6 @@ func (vector Vector) String() string {
strings.Join(labelStrings, ","),
sample.Value, sample.Timestamp))
}
sort.Strings(metricStrings)
return strings.Join(metricStrings, "\n")
}

View file

@ -25,12 +25,12 @@ import (
var testEvalTime = testStartTime.Add(testDuration5m * 10)
// Expected output needs to be alphabetically sorted (labels within one line
// must be sorted and lines between each other must be sorted too).
// Labels in expected output need to be alphabetically sorted.
var expressionTests = []struct {
expr string
output []string
shouldFail bool
checkOrder bool
fullRanges int
intervalRanges int
}{
@ -179,6 +179,36 @@ var expressionTests = []struct {
},
fullRanges: 8,
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'}",
output: []string{
@ -232,7 +262,7 @@ func TestExpressions(t *testing.T) {
storeMatrix(tieredStorage, testMatrix)
tieredStorage.Flush()
for _, exprTest := range expressionTests {
for i, exprTest := range expressionTests {
expectedLines := annotateWithTime(exprTest.output)
testExpr, err := LoadExprFromString(exprTest.expr)
@ -241,47 +271,56 @@ func TestExpressions(t *testing.T) {
if exprTest.shouldFail {
continue
}
t.Errorf("Error during parsing: %v", err)
t.Errorf("Expression: %v", exprTest.expr)
t.Errorf("%d Error during parsing: %v", i, err)
t.Errorf("%d Expression: %v", i, exprTest.expr)
} else {
if exprTest.shouldFail {
t.Errorf("Test should fail, but didn't")
t.Errorf("%d Test should fail, but didn't", i)
}
failed := false
resultStr := ast.EvalToString(testExpr, testEvalTime, ast.TEXT)
resultLines := strings.Split(resultStr, "\n")
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
}
for _, expectedSample := range expectedLines {
found := false
for _, actualSample := range resultLines {
if actualSample == expectedSample {
found = true
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
}
}
if !found {
t.Errorf("Couldn't find expected sample in output: '%v'",
expectedSample)
failed = true
} else {
for j, expectedSample := range expectedLines {
found := false
for _, actualSample := range resultLines {
if actualSample == expectedSample {
found = true
}
}
if !found {
t.Errorf("%d.%d Couldn't find expected sample in output: '%v'", i, j, expectedSample)
failed = true
}
}
}
analyzer := ast.NewQueryAnalyzer()
analyzer.AnalyzeQueries(testExpr)
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
}
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
}
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))
}
}
}

31
utility/sort.go Normal file
View file

@ -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)
}