mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-26 22:19:40 -08:00
289 lines
8.5 KiB
Go
289 lines
8.5 KiB
Go
// 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 rules
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/prometheus/prometheus/rules/ast"
|
|
"github.com/prometheus/prometheus/storage/metric"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
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).
|
|
var expressionTests = []struct {
|
|
expr string
|
|
output []string
|
|
shouldFail bool
|
|
fullRanges int
|
|
intervalRanges int
|
|
}{
|
|
{
|
|
expr: "SUM(http_requests)",
|
|
output: []string{"http_requests{} => 3600 @[%v]"},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 1000 @[%v]",
|
|
"http_requests{job='app-server'} => 2600 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job, group)",
|
|
output: []string{
|
|
"http_requests{group='canary',job='api-server'} => 700 @[%v]",
|
|
"http_requests{group='canary',job='app-server'} => 1500 @[%v]",
|
|
"http_requests{group='production',job='api-server'} => 300 @[%v]",
|
|
"http_requests{group='production',job='app-server'} => 1100 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "AVG(http_requests) BY (job)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 250 @[%v]",
|
|
"http_requests{job='app-server'} => 650 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "MIN(http_requests) BY (job)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 100 @[%v]",
|
|
"http_requests{job='app-server'} => 500 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "MAX(http_requests) BY (job)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 400 @[%v]",
|
|
"http_requests{job='app-server'} => 800 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) - count(http_requests)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 992 @[%v]",
|
|
"http_requests{job='app-server'} => 2592 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) - 2",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 998 @[%v]",
|
|
"http_requests{job='app-server'} => 2598 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) % 3",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 1 @[%v]",
|
|
"http_requests{job='app-server'} => 2 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) / 0",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => +Inf @[%v]",
|
|
"http_requests{job='app-server'} => +Inf @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) > 1000",
|
|
output: []string{
|
|
"http_requests{job='app-server'} => 2600 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) <= 1000",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 1000 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) != 1000",
|
|
output: []string{
|
|
"http_requests{job='app-server'} => 2600 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) == 1000",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 1000 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "SUM(http_requests) BY (job) + SUM(http_requests) BY (job)",
|
|
output: []string{
|
|
"http_requests{job='api-server'} => 2000 @[%v]",
|
|
"http_requests{job='app-server'} => 5200 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 8,
|
|
}, {
|
|
expr: "http_requests{job='api-server', group='canary'}",
|
|
output: []string{
|
|
"http_requests{group='canary',instance='0',job='api-server'} => 300 @[%v]",
|
|
"http_requests{group='canary',instance='1',job='api-server'} => 400 @[%v]",
|
|
},
|
|
fullRanges: 0,
|
|
intervalRanges: 2,
|
|
}, {
|
|
expr: "http_requests{job='api-server', group='canary'} + delta(http_requests{job='api-server'}[5m], 1)",
|
|
output: []string{
|
|
"http_requests{group='canary',instance='0',job='api-server'} => 330 @[%v]",
|
|
"http_requests{group='canary',instance='1',job='api-server'} => 440 @[%v]",
|
|
},
|
|
fullRanges: 4,
|
|
intervalRanges: 0,
|
|
}, {
|
|
expr: "delta(http_requests[25m], 1)",
|
|
output: []string{
|
|
"http_requests{group='canary',instance='0',job='api-server'} => 150 @[%v]",
|
|
"http_requests{group='canary',instance='0',job='app-server'} => 350 @[%v]",
|
|
"http_requests{group='canary',instance='1',job='api-server'} => 200 @[%v]",
|
|
"http_requests{group='canary',instance='1',job='app-server'} => 400 @[%v]",
|
|
"http_requests{group='production',instance='0',job='api-server'} => 50 @[%v]",
|
|
"http_requests{group='production',instance='0',job='app-server'} => 250 @[%v]",
|
|
"http_requests{group='production',instance='1',job='api-server'} => 100 @[%v]",
|
|
"http_requests{group='production',instance='1',job='app-server'} => 300 @[%v]",
|
|
},
|
|
fullRanges: 8,
|
|
intervalRanges: 0,
|
|
// Invalid expressions that should fail to parse.
|
|
}, {
|
|
expr: "",
|
|
shouldFail: true,
|
|
}, {
|
|
expr: "1 - http_requests",
|
|
shouldFail: true,
|
|
}, {
|
|
expr: "http_requests['1m']",
|
|
shouldFail: true,
|
|
},
|
|
}
|
|
|
|
func annotateWithTime(lines []string) []string {
|
|
annotatedLines := []string{}
|
|
for _, line := range lines {
|
|
annotatedLines = append(annotatedLines, fmt.Sprintf(line, testEvalTime))
|
|
}
|
|
return annotatedLines
|
|
}
|
|
|
|
func vectorComparisonString(expected []string, actual []string) string {
|
|
separator := "\n--------------\n"
|
|
return fmt.Sprintf("Expected:%v%v%v\nActual:%v%v%v ",
|
|
separator,
|
|
strings.Join(expected, "\n"),
|
|
separator,
|
|
separator,
|
|
strings.Join(actual, "\n"),
|
|
separator)
|
|
}
|
|
|
|
func TestExpressions(t *testing.T) {
|
|
temporaryDirectory, err := ioutil.TempDir("", "rule_expression_tests")
|
|
if err != nil {
|
|
t.Errorf("Could not create temporary directory: %q\n", err)
|
|
return
|
|
}
|
|
tieredStorage := metric.NewTieredStorage(5000, 5000, 100, time.Second*30, time.Second*1, time.Second*20, temporaryDirectory)
|
|
go tieredStorage.Serve()
|
|
defer func() {
|
|
tieredStorage.Close()
|
|
if err = os.RemoveAll(temporaryDirectory); err != nil {
|
|
t.Errorf("Could not remove temporary directory: %q\n", err)
|
|
}
|
|
}()
|
|
|
|
ast.SetStorage(tieredStorage)
|
|
|
|
storeMatrix(tieredStorage, testMatrix)
|
|
tieredStorage.Flush()
|
|
|
|
for _, exprTest := range expressionTests {
|
|
expectedLines := annotateWithTime(exprTest.output)
|
|
|
|
testExpr, err := LoadExprFromString(exprTest.expr)
|
|
|
|
if err != nil {
|
|
if exprTest.shouldFail {
|
|
continue
|
|
}
|
|
t.Errorf("Error during parsing: %v", err)
|
|
t.Errorf("Expression: %v", exprTest.expr)
|
|
} else {
|
|
if exprTest.shouldFail {
|
|
t.Errorf("Test should fail, but didn't")
|
|
}
|
|
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")
|
|
failed = true
|
|
}
|
|
for _, expectedSample := range expectedLines {
|
|
found := false
|
|
for _, actualSample := range resultLines {
|
|
if actualSample == expectedSample {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Couldn't find expected sample in output: '%v'",
|
|
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))
|
|
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))
|
|
failed = true
|
|
}
|
|
|
|
if failed {
|
|
t.Errorf("Expression: %v\n%v", exprTest.expr, vectorComparisonString(expectedLines, resultLines))
|
|
}
|
|
}
|
|
}
|
|
}
|