2015-01-21 11:07:45 -08:00
// Copyright 2013 The Prometheus Authors
2013-02-07 02:49:04 -08:00
// 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.
2013-01-07 14:24:26 -08:00
package rules
import (
2017-05-18 09:47:00 -07:00
"context"
"math"
2017-11-23 04:04:54 -08:00
"sort"
2013-01-07 14:24:26 -08:00
"testing"
2013-03-21 10:06:15 -07:00
"time"
2013-06-25 05:02:27 -07:00
2017-08-11 11:45:52 -07:00
"github.com/go-kit/kit/log"
2015-08-20 08:18:46 -07:00
"github.com/prometheus/common/model"
2013-06-25 05:02:27 -07:00
2016-12-29 08:31:14 -08:00
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/timestamp"
2017-05-18 09:47:00 -07:00
"github.com/prometheus/prometheus/pkg/value"
2015-03-30 10:43:19 -07:00
"github.com/prometheus/prometheus/promql"
2017-05-18 09:47:00 -07:00
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/testutil"
2013-01-07 14:24:26 -08:00
)
2015-06-30 02:51:05 -07:00
func TestAlertingRule ( t * testing . T ) {
suite , err := promql . NewTest ( t , `
load 5 m
2016-07-12 09:11:31 -07:00
http_requests { job = "app-server" , instance = "0" , group = "canary" , severity = "overwrite-me" } 75 85 95 105 105 95 85
http_requests { job = "app-server" , instance = "1" , group = "canary" , severity = "overwrite-me" } 80 90 100 110 120 130 140
2015-06-30 02:51:05 -07:00
` )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2015-06-30 02:51:05 -07:00
defer suite . Close ( )
2015-03-30 10:43:19 -07:00
2017-11-11 02:29:47 -08:00
err = suite . Run ( )
testutil . Ok ( t , err )
2013-01-07 14:24:26 -08:00
2015-06-30 02:51:05 -07:00
expr , err := promql . ParseExpr ( ` http_requests { group="canary", job="app-server"} < 100 ` )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2013-04-22 15:26:59 -07:00
2015-06-30 02:51:05 -07:00
rule := NewAlertingRule (
"HTTPRequestRateLow" ,
expr ,
time . Minute ,
2016-12-29 08:31:14 -08:00
labels . FromStrings ( "severity" , "{{\"c\"}}ritical" ) ,
2017-08-11 11:45:52 -07:00
nil , nil ,
2015-06-30 02:51:05 -07:00
)
2017-11-23 04:04:54 -08:00
result := promql . Vector {
{
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
{
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"group" , "canary" ,
"instance" , "1" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
{
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "firing" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
{
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "firing" ,
"group" , "canary" ,
"instance" , "1" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
}
2015-06-30 02:51:05 -07:00
2016-12-29 08:31:14 -08:00
baseTime := time . Unix ( 0 , 0 )
2015-06-30 02:51:05 -07:00
var tests = [ ] struct {
time time . Duration
2017-11-23 04:04:54 -08:00
result promql . Vector
2015-06-30 02:51:05 -07:00
} {
{
2017-11-23 04:04:54 -08:00
time : 0 ,
result : result [ : 2 ] ,
2015-06-30 02:51:05 -07:00
} , {
2017-11-23 04:04:54 -08:00
time : 5 * time . Minute ,
result : result [ 2 : ] ,
2015-06-30 02:51:05 -07:00
} , {
2017-11-23 04:04:54 -08:00
time : 10 * time . Minute ,
result : result [ 2 : 3 ] ,
2015-03-30 10:43:19 -07:00
} ,
2013-04-24 02:51:40 -07:00
{
2017-05-19 09:02:25 -07:00
time : 15 * time . Minute ,
2017-11-23 04:04:54 -08:00
result : nil ,
2013-04-24 02:51:40 -07:00
} ,
{
2015-06-30 02:51:05 -07:00
time : 20 * time . Minute ,
2017-11-23 04:04:54 -08:00
result : nil ,
2016-02-04 20:42:55 -08:00
} ,
{
2017-11-23 04:04:54 -08:00
time : 25 * time . Minute ,
result : result [ : 1 ] ,
2016-02-04 20:42:55 -08:00
} ,
{
2017-11-23 04:04:54 -08:00
time : 30 * time . Minute ,
result : result [ 2 : 3 ] ,
2013-04-24 02:51:40 -07:00
} ,
}
2015-03-30 10:43:19 -07:00
2015-06-30 02:51:05 -07:00
for i , test := range tests {
2017-11-23 04:04:54 -08:00
t . Logf ( "case %d" , i )
2016-12-29 08:31:14 -08:00
evalTime := baseTime . Add ( test . time )
2015-03-30 10:43:19 -07:00
2018-01-09 08:44:23 -08:00
res , err := rule . Eval ( suite . Context ( ) , evalTime , EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) , nil )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2015-03-30 10:43:19 -07:00
2017-11-23 04:04:54 -08:00
for i := range test . result {
test . result [ i ] . T = timestamp . FromTime ( evalTime )
2013-04-24 02:51:40 -07:00
}
2017-11-23 04:04:54 -08:00
testutil . Assert ( t , len ( test . result ) == len ( res ) , "%d. Number of samples in expected and actual output don't match (%d vs. %d)" , i , len ( test . result ) , len ( res ) )
sort . Slice ( res , func ( i , j int ) bool {
return labels . Compare ( res [ i ] . Metric , res [ j ] . Metric ) < 0
} )
testutil . Equals ( t , test . result , res )
2016-08-01 15:32:01 -07:00
for _ , aa := range rule . ActiveAlerts ( ) {
2017-11-11 02:29:47 -08:00
testutil . Assert ( t , aa . Labels . Get ( model . MetricNameLabel ) == "" , "%s label set on active alert: %s" , model . MetricNameLabel , aa . Labels )
2016-08-01 15:32:01 -07:00
}
2013-04-24 02:51:40 -07:00
}
}
2015-06-30 02:51:05 -07:00
2017-05-18 09:47:00 -07:00
func TestStaleness ( t * testing . T ) {
storage := testutil . NewStorage ( t )
defer storage . Close ( )
2018-01-09 08:44:23 -08:00
engine := promql . NewEngine ( nil , nil , 10 , 10 * time . Second )
2017-05-18 09:47:00 -07:00
opts := & ManagerOptions {
2018-01-09 08:44:23 -08:00
QueryFunc : EngineQueryFunc ( engine , storage ) ,
2017-11-23 04:04:54 -08:00
Appendable : storage ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
2017-05-18 09:47:00 -07:00
}
expr , err := promql . ParseExpr ( "a + 1" )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2017-05-18 09:47:00 -07:00
rule := NewRecordingRule ( "a_plus_one" , expr , labels . Labels { } )
2017-06-14 02:48:39 -07:00
group := NewGroup ( "default" , "" , time . Second , [ ] Rule { rule } , opts )
2017-05-18 09:47:00 -07:00
// A time series that has two samples and then goes stale.
app , _ := storage . Appender ( )
app . Add ( labels . FromStrings ( model . MetricNameLabel , "a" ) , 0 , 1 )
app . Add ( labels . FromStrings ( model . MetricNameLabel , "a" ) , 1000 , 2 )
app . Add ( labels . FromStrings ( model . MetricNameLabel , "a" ) , 2000 , math . Float64frombits ( value . StaleNaN ) )
2017-11-11 02:29:47 -08:00
err = app . Commit ( )
testutil . Ok ( t , err )
2017-05-18 09:47:00 -07:00
2017-11-23 23:59:05 -08:00
ctx := context . Background ( )
2017-05-18 09:47:00 -07:00
// Execute 3 times, 1 second apart.
2017-11-23 23:59:05 -08:00
group . Eval ( ctx , time . Unix ( 0 , 0 ) )
group . Eval ( ctx , time . Unix ( 1 , 0 ) )
group . Eval ( ctx , time . Unix ( 2 , 0 ) )
2017-05-18 09:47:00 -07:00
2017-10-04 12:04:15 -07:00
querier , err := storage . Querier ( context . Background ( ) , 0 , 2000 )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2017-10-09 09:03:33 -07:00
defer querier . Close ( )
2017-11-23 04:50:06 -08:00
matcher , err := labels . NewMatcher ( labels . MatchEqual , model . MetricNameLabel , "a_plus_one" )
testutil . Ok ( t , err )
2018-01-09 08:44:23 -08:00
set , err := querier . Select ( nil , matcher )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2017-11-23 04:50:06 -08:00
samples , err := readSeriesSet ( set )
testutil . Ok ( t , err )
2017-05-18 09:47:00 -07:00
metric := labels . FromStrings ( model . MetricNameLabel , "a_plus_one" ) . String ( )
metricSample , ok := samples [ metric ]
2017-11-11 02:29:47 -08:00
testutil . Assert ( t , ok , "Series %s not returned." , metric )
testutil . Assert ( t , value . IsStaleNaN ( metricSample [ 2 ] . V ) , "Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( metricSample [ 2 ] . V ) )
2017-05-18 09:47:00 -07:00
metricSample [ 2 ] . V = 42 // reflect.DeepEqual cannot handle NaN.
want := map [ string ] [ ] promql . Point {
metric : [ ] promql . Point { { 0 , 2 } , { 1000 , 3 } , { 2000 , 42 } } ,
}
2017-11-11 02:29:47 -08:00
testutil . Equals ( t , want , samples )
2017-05-18 09:47:00 -07:00
}
// Convert a SeriesSet into a form useable with reflect.DeepEqual.
func readSeriesSet ( ss storage . SeriesSet ) ( map [ string ] [ ] promql . Point , error ) {
result := map [ string ] [ ] promql . Point { }
for ss . Next ( ) {
series := ss . At ( )
points := [ ] promql . Point { }
it := series . Iterator ( )
for it . Next ( ) {
t , v := it . At ( )
points = append ( points , promql . Point { T : t , V : v } )
}
name := series . Labels ( ) . String ( )
result [ name ] = points
}
2017-05-19 08:43:59 -07:00
return result , ss . Err ( )
}
func TestCopyState ( t * testing . T ) {
oldGroup := & Group {
rules : [ ] Rule {
2017-08-11 11:45:52 -07:00
NewAlertingRule ( "alert" , nil , 0 , nil , nil , nil ) ,
2017-05-19 08:43:59 -07:00
NewRecordingRule ( "rule1" , nil , nil ) ,
NewRecordingRule ( "rule2" , nil , nil ) ,
NewRecordingRule ( "rule3" , nil , nil ) ,
NewRecordingRule ( "rule3" , nil , nil ) ,
} ,
seriesInPreviousEval : [ ] map [ string ] labels . Labels {
map [ string ] labels . Labels { "a" : nil } ,
map [ string ] labels . Labels { "r1" : nil } ,
map [ string ] labels . Labels { "r2" : nil } ,
map [ string ] labels . Labels { "r3a" : nil } ,
map [ string ] labels . Labels { "r3b" : nil } ,
} ,
2018-07-17 20:54:33 -07:00
evaluationDuration : time . Second ,
2017-05-19 08:43:59 -07:00
}
oldGroup . rules [ 0 ] . ( * AlertingRule ) . active [ 42 ] = nil
newGroup := & Group {
rules : [ ] Rule {
NewRecordingRule ( "rule3" , nil , nil ) ,
NewRecordingRule ( "rule3" , nil , nil ) ,
NewRecordingRule ( "rule3" , nil , nil ) ,
2017-08-11 11:45:52 -07:00
NewAlertingRule ( "alert" , nil , 0 , nil , nil , nil ) ,
2017-05-19 08:43:59 -07:00
NewRecordingRule ( "rule1" , nil , nil ) ,
NewRecordingRule ( "rule4" , nil , nil ) ,
} ,
seriesInPreviousEval : make ( [ ] map [ string ] labels . Labels , 6 ) ,
}
2018-07-18 06:14:38 -07:00
newGroup . CopyState ( oldGroup )
2017-05-19 08:43:59 -07:00
want := [ ] map [ string ] labels . Labels {
map [ string ] labels . Labels { "r3a" : nil } ,
map [ string ] labels . Labels { "r3b" : nil } ,
nil ,
map [ string ] labels . Labels { "a" : nil } ,
map [ string ] labels . Labels { "r1" : nil } ,
nil ,
}
2017-11-11 02:29:47 -08:00
testutil . Equals ( t , want , newGroup . seriesInPreviousEval )
testutil . Equals ( t , oldGroup . rules [ 0 ] , newGroup . rules [ 3 ] )
2018-07-17 20:54:33 -07:00
testutil . Equals ( t , oldGroup . evaluationDuration , newGroup . evaluationDuration )
2017-05-18 09:47:00 -07:00
}
2017-11-01 04:58:00 -07:00
2017-11-23 06:48:14 -08:00
func TestUpdate ( t * testing . T ) {
2018-06-22 07:21:04 -07:00
files := [ ] string { "fixtures/rules.yaml" }
2017-11-01 04:58:00 -07:00
expected := map [ string ] labels . Labels {
2017-11-23 06:48:14 -08:00
"test" : labels . FromStrings ( "name" , "value" ) ,
2017-11-01 04:58:00 -07:00
}
ruleManager := NewManager ( & ManagerOptions {
2017-11-23 23:59:05 -08:00
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
2017-11-01 04:58:00 -07:00
} )
ruleManager . Run ( )
2018-06-22 07:21:04 -07:00
err := ruleManager . Update ( 10 * time . Second , files )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2018-06-22 07:21:04 -07:00
testutil . Assert ( t , len ( ruleManager . groups ) > 0 , "expected non-empty rule groups" )
2017-11-01 04:58:00 -07:00
for _ , g := range ruleManager . groups {
g . seriesInPreviousEval = [ ] map [ string ] labels . Labels {
expected ,
}
}
2018-06-22 07:21:04 -07:00
err = ruleManager . Update ( 10 * time . Second , files )
2017-11-11 02:29:47 -08:00
testutil . Ok ( t , err )
2017-11-01 04:58:00 -07:00
for _ , g := range ruleManager . groups {
for _ , actual := range g . seriesInPreviousEval {
2017-11-11 02:29:47 -08:00
testutil . Equals ( t , expected , actual )
2017-11-01 04:58:00 -07:00
}
}
}