mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Add sort() and sort_desc() expression language functions.
This commit is contained in:
parent
adeabca230
commit
6cb3c51d24
|
@ -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) {
|
||||
|
|
|
@ -88,7 +88,6 @@ func (vector Vector) String() string {
|
|||
strings.Join(labelStrings, ","),
|
||||
sample.Value, sample.Timestamp))
|
||||
}
|
||||
sort.Strings(metricStrings)
|
||||
return strings.Join(metricStrings, "\n")
|
||||
}
|
||||
|
||||
|
|
|
@ -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,21 +271,30 @@ 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 {
|
||||
|
||||
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
|
||||
for _, actualSample := range resultLines {
|
||||
if actualSample == expectedSample {
|
||||
|
@ -263,25 +302,25 @@ func TestExpressions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Couldn't find expected sample in output: '%v'",
|
||||
expectedSample)
|
||||
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
31
utility/sort.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue