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"
2019-12-19 02:41:11 -08:00
"fmt"
2022-03-14 06:20:07 -07:00
"io/fs"
2017-05-18 09:47:00 -07:00
"math"
2019-12-19 02:41:11 -08:00
"os"
2022-03-14 06:20:07 -07:00
"path"
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
2021-06-11 09:17:59 -07:00
"github.com/go-kit/log"
2020-01-27 04:41:32 -08:00
"github.com/prometheus/client_golang/prometheus"
2015-08-20 08:18:46 -07:00
"github.com/prometheus/common/model"
2020-10-29 02:43:23 -07:00
"github.com/stretchr/testify/require"
2022-07-19 03:58:37 -07:00
"go.uber.org/atomic"
2020-07-27 01:38:08 -07:00
"go.uber.org/goleak"
2022-08-31 06:50:38 -07:00
"gopkg.in/yaml.v2"
2013-06-25 05:02:27 -07:00
2021-11-08 06:23:17 -08:00
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/rulefmt"
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/model/value"
2015-03-30 10:43:19 -07:00
"github.com/prometheus/prometheus/promql"
2020-02-03 10:23:07 -08:00
"github.com/prometheus/prometheus/promql/parser"
2017-05-18 09:47:00 -07:00
"github.com/prometheus/prometheus/storage"
2021-11-28 23:54:23 -08:00
"github.com/prometheus/prometheus/tsdb/chunkenc"
2023-02-10 03:39:33 -08:00
"github.com/prometheus/prometheus/tsdb/tsdbutil"
2019-08-08 18:35:39 -07:00
"github.com/prometheus/prometheus/util/teststorage"
2013-01-07 14:24:26 -08:00
)
2020-07-27 01:38:08 -07:00
func TestMain ( m * testing . M ) {
goleak . VerifyTestMain ( m )
}
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
` )
2020-10-29 02:43:23 -07:00
require . NoError ( 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 ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2013-01-07 14:24:26 -08:00
2020-02-03 10:23:07 -08:00
expr , err := parser . ParseExpr ( ` http_requests { group="canary", job="app-server"} < 100 ` )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2013-04-22 15:26:59 -07:00
2015-06-30 02:51:05 -07:00
rule := NewAlertingRule (
"HTTPRequestRateLow" ,
expr ,
time . Minute ,
2023-01-09 03:21:38 -08:00
0 ,
2016-12-29 08:31:14 -08:00
labels . FromStrings ( "severity" , "{{\"c\"}}ritical" ) ,
2022-07-21 09:44:35 -07:00
labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ,
2015-06-30 02:51:05 -07:00
)
2017-11-23 04:04:54 -08:00
result := promql . Vector {
2021-11-17 10:57:31 -08:00
promql . Sample {
2017-11-23 04:04:54 -08:00
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2021-11-17 10:57:31 -08:00
promql . Sample {
2017-11-23 04:04:54 -08:00
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "pending" ,
"group" , "canary" ,
"instance" , "1" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2021-11-17 10:57:31 -08:00
promql . Sample {
2017-11-23 04:04:54 -08:00
Metric : labels . FromStrings (
"__name__" , "ALERTS" ,
"alertname" , "HTTPRequestRateLow" ,
"alertstate" , "firing" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2021-11-17 10:57:31 -08:00
promql . Sample {
2017-11-23 04:04:54 -08:00
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 )
2021-10-22 01:06:44 -07:00
tests := [ ] struct {
2015-06-30 02:51:05 -07:00
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 ] ,
2021-10-22 01:06:44 -07:00
} ,
{
2017-11-23 04:04:54 -08:00
time : 5 * time . Minute ,
result : result [ 2 : ] ,
2021-10-22 01:06:44 -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
2022-03-14 06:20:07 -07:00
res , err := rule . Eval ( suite . Context ( ) , 0 , evalTime , EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) , nil , 0 )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2015-03-30 10:43:19 -07:00
2018-08-02 03:18:24 -07:00
var filteredRes promql . Vector // After removing 'ALERTS_FOR_STATE' samples.
for _ , smpl := range res {
smplName := smpl . Metric . Get ( "__name__" )
if smplName == "ALERTS" {
filteredRes = append ( filteredRes , smpl )
} else {
// If not 'ALERTS', it has to be 'ALERTS_FOR_STATE'.
2020-10-29 02:43:23 -07:00
require . Equal ( t , smplName , "ALERTS_FOR_STATE" )
2018-08-02 03:18:24 -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
}
2020-10-29 02:43:23 -07:00
require . Equal ( t , len ( test . result ) , len ( filteredRes ) , "%d. Number of samples in expected and actual output don't match (%d vs. %d)" , i , len ( test . result ) , len ( res ) )
2017-11-23 04:04:54 -08:00
2018-08-02 03:18:24 -07:00
sort . Slice ( filteredRes , func ( i , j int ) bool {
return labels . Compare ( filteredRes [ i ] . Metric , filteredRes [ j ] . Metric ) < 0
2017-11-23 04:04:54 -08:00
} )
2020-10-29 02:43:23 -07:00
require . Equal ( t , test . result , filteredRes )
2016-08-01 15:32:01 -07:00
for _ , aa := range rule . ActiveAlerts ( ) {
2020-10-29 02:43:23 -07:00
require . Zero ( 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
2018-08-02 03:18:24 -07:00
func TestForStateAddSamples ( t * testing . T ) {
2022-03-14 06:20:07 -07:00
for _ , evalDelay := range [ ] time . Duration { 0 , time . Minute } {
t . Run ( fmt . Sprintf ( "evalDelay %s" , evalDelay . String ( ) ) , func ( t * testing . T ) {
suite , err := promql . NewTest ( t , `
2018-08-02 03:18:24 -07:00
load 5 m
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
` )
2022-03-14 06:20:07 -07:00
require . NoError ( t , err )
defer suite . Close ( )
err = suite . Run ( )
require . NoError ( t , err )
expr , err := parser . ParseExpr ( ` http_requests { group="canary", job="app-server"} < 100 ` )
require . NoError ( t , err )
rule := NewAlertingRule (
"HTTPRequestRateLow" ,
expr ,
time . Minute ,
2023-01-26 09:41:17 -08:00
0 ,
2022-03-14 06:20:07 -07:00
labels . FromStrings ( "severity" , "{{\"c\"}}ritical" ) ,
2022-10-05 08:01:08 -07:00
labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ,
2022-03-14 06:20:07 -07:00
)
result := promql . Vector {
2022-11-22 09:21:08 -08:00
promql . Sample {
2022-03-14 06:20:07 -07:00
Metric : labels . FromStrings (
"__name__" , "ALERTS_FOR_STATE" ,
"alertname" , "HTTPRequestRateLow" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2022-11-22 09:21:08 -08:00
promql . Sample {
2022-03-14 06:20:07 -07:00
Metric : labels . FromStrings (
"__name__" , "ALERTS_FOR_STATE" ,
"alertname" , "HTTPRequestRateLow" ,
"group" , "canary" ,
"instance" , "1" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2022-11-22 09:21:08 -08:00
promql . Sample {
2022-03-14 06:20:07 -07:00
Metric : labels . FromStrings (
"__name__" , "ALERTS_FOR_STATE" ,
"alertname" , "HTTPRequestRateLow" ,
"group" , "canary" ,
"instance" , "0" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
2022-11-22 09:21:08 -08:00
promql . Sample {
2022-03-14 06:20:07 -07:00
Metric : labels . FromStrings (
"__name__" , "ALERTS_FOR_STATE" ,
"alertname" , "HTTPRequestRateLow" ,
"group" , "canary" ,
"instance" , "1" ,
"job" , "app-server" ,
"severity" , "critical" ,
) ,
Point : promql . Point { V : 1 } ,
} ,
}
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
baseTime := time . Unix ( 0 , 0 )
tests := [ ] struct {
time time . Duration
result promql . Vector
persistThisTime bool // If true, it means this 'time' is persisted for 'for'.
} {
{
time : 0 ,
result : append ( promql . Vector { } , result [ : 2 ] ... ) ,
persistThisTime : true ,
} ,
{
time : 5 * time . Minute ,
result : append ( promql . Vector { } , result [ 2 : ] ... ) ,
} ,
{
time : 10 * time . Minute ,
result : append ( promql . Vector { } , result [ 2 : 3 ] ... ) ,
} ,
{
time : 15 * time . Minute ,
result : nil ,
} ,
{
time : 20 * time . Minute ,
result : nil ,
} ,
{
time : 25 * time . Minute ,
result : append ( promql . Vector { } , result [ : 1 ] ... ) ,
persistThisTime : true ,
} ,
{
time : 30 * time . Minute ,
result : append ( promql . Vector { } , result [ 2 : 3 ] ... ) ,
} ,
}
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
var forState float64
for i , test := range tests {
t . Logf ( "case %d" , i )
evalTime := baseTime . Add ( test . time ) . Add ( evalDelay )
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
if test . persistThisTime {
forState = float64 ( evalTime . Unix ( ) )
}
if test . result == nil {
forState = float64 ( value . StaleNaN )
}
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
res , err := rule . Eval ( suite . Context ( ) , evalDelay , evalTime , EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) , nil , 0 )
require . NoError ( t , err )
var filteredRes promql . Vector // After removing 'ALERTS' samples.
for _ , smpl := range res {
smplName := smpl . Metric . Get ( "__name__" )
if smplName == "ALERTS_FOR_STATE" {
filteredRes = append ( filteredRes , smpl )
} else {
// If not 'ALERTS_FOR_STATE', it has to be 'ALERTS'.
require . Equal ( t , smplName , "ALERTS" )
}
}
for i := range test . result {
test . result [ i ] . T = timestamp . FromTime ( evalTime . Add ( - evalDelay ) )
// Updating the expected 'for' state.
if test . result [ i ] . V >= 0 {
test . result [ i ] . V = forState
}
}
require . Equal ( t , len ( test . result ) , len ( filteredRes ) , "%d. Number of samples in expected and actual output don't match (%d vs. %d)" , i , len ( test . result ) , len ( res ) )
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
sort . Slice ( filteredRes , func ( i , j int ) bool {
return labels . Compare ( filteredRes [ i ] . Metric , filteredRes [ j ] . Metric ) < 0
} )
require . Equal ( t , test . result , filteredRes )
2018-08-02 03:18:24 -07:00
2022-03-14 06:20:07 -07:00
for _ , aa := range rule . ActiveAlerts ( ) {
require . Zero ( t , aa . Labels . Get ( model . MetricNameLabel ) , "%s label set on active alert: %s" , model . MetricNameLabel , aa . Labels )
}
2018-08-02 03:18:24 -07:00
}
} )
}
}
2018-08-16 10:26:15 -07:00
// sortAlerts sorts `[]*Alert` w.r.t. the Labels.
func sortAlerts ( items [ ] * Alert ) {
sort . Slice ( items , func ( i , j int ) bool {
return labels . Compare ( items [ i ] . Labels , items [ j ] . Labels ) <= 0
} )
}
2018-08-02 03:18:24 -07:00
2018-08-16 10:26:15 -07:00
func TestForStateRestore ( t * testing . T ) {
2018-08-02 03:18:24 -07:00
suite , err := promql . NewTest ( t , `
load 5 m
http_requests { job = "app-server" , instance = "0" , group = "canary" , severity = "overwrite-me" } 75 85 50 0 0 25 0 0 40 0 120
http_requests { job = "app-server" , instance = "1" , group = "canary" , severity = "overwrite-me" } 125 90 60 0 0 25 0 0 40 0 130
` )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-08-02 03:18:24 -07:00
defer suite . Close ( )
err = suite . Run ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-08-02 03:18:24 -07:00
2020-02-03 10:23:07 -08:00
expr , err := parser . ParseExpr ( ` http_requests { group="canary", job="app-server"} < 100 ` )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-08-02 03:18:24 -07:00
opts := & ManagerOptions {
2018-08-04 12:31:12 -07:00
QueryFunc : EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) ,
Appendable : suite . Storage ( ) ,
2020-06-26 11:06:36 -07:00
Queryable : suite . Storage ( ) ,
2018-08-04 12:31:12 -07:00
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
NotifyFunc : func ( ctx context . Context , expr string , alerts ... * Alert ) { } ,
2018-08-02 03:18:24 -07:00
OutageTolerance : 30 * time . Minute ,
ForGracePeriod : 10 * time . Minute ,
}
alertForDuration := 25 * time . Minute
// Initial run before prometheus goes down.
rule := NewAlertingRule (
"HTTPRequestRateLow" ,
expr ,
alertForDuration ,
2023-01-09 03:21:38 -08:00
0 ,
2018-08-02 03:18:24 -07:00
labels . FromStrings ( "severity" , "critical" ) ,
2022-07-21 09:44:35 -07:00
labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ,
2018-08-02 03:18:24 -07:00
)
2020-02-12 07:22:18 -08:00
group := NewGroup ( GroupOptions {
Name : "default" ,
Interval : time . Second ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
} )
2018-08-02 03:18:24 -07:00
groups := make ( map [ string ] * Group )
groups [ "default;" ] = group
initialRuns := [ ] time . Duration { 0 , 5 * time . Minute }
baseTime := time . Unix ( 0 , 0 )
for _ , duration := range initialRuns {
evalTime := baseTime . Add ( duration )
group . Eval ( suite . Context ( ) , evalTime )
}
exp := rule . ActiveAlerts ( )
for _ , aa := range exp {
2020-10-29 02:43:23 -07:00
require . Zero ( t , aa . Labels . Get ( model . MetricNameLabel ) , "%s label set on active alert: %s" , model . MetricNameLabel , aa . Labels )
2018-08-02 03:18:24 -07:00
}
sort . Slice ( exp , func ( i , j int ) bool {
return labels . Compare ( exp [ i ] . Labels , exp [ j ] . Labels ) < 0
} )
// Prometheus goes down here. We create new rules and groups.
type testInput struct {
restoreDuration time . Duration
alerts [ ] * Alert
num int
noRestore bool
gracePeriod bool
downDuration time . Duration
}
tests := [ ] testInput {
{
// Normal restore (alerts were not firing).
2018-08-16 10:26:15 -07:00
restoreDuration : 15 * time . Minute ,
2018-08-02 03:18:24 -07:00
alerts : rule . ActiveAlerts ( ) ,
2018-08-16 10:26:15 -07:00
downDuration : 10 * time . Minute ,
2018-08-02 03:18:24 -07:00
} ,
{
// Testing Outage Tolerance.
restoreDuration : 40 * time . Minute ,
noRestore : true ,
num : 2 ,
} ,
{
// No active alerts.
restoreDuration : 50 * time . Minute ,
alerts : [ ] * Alert { } ,
} ,
}
2022-03-14 06:20:07 -07:00
testFunc := func ( tst testInput , evalDelay time . Duration ) {
2018-08-02 03:18:24 -07:00
newRule := NewAlertingRule (
"HTTPRequestRateLow" ,
expr ,
alertForDuration ,
2023-01-09 03:21:38 -08:00
0 ,
2018-08-02 03:18:24 -07:00
labels . FromStrings ( "severity" , "critical" ) ,
2022-07-21 09:44:35 -07:00
labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , false , nil ,
2018-08-02 03:18:24 -07:00
)
2020-02-12 07:22:18 -08:00
newGroup := NewGroup ( GroupOptions {
2022-03-14 06:20:07 -07:00
Name : "default" ,
Interval : time . Second ,
Rules : [ ] Rule { newRule } ,
ShouldRestore : true ,
Opts : opts ,
EvaluationDelay : & evalDelay ,
2020-02-12 07:22:18 -08:00
} )
2018-08-02 03:18:24 -07:00
newGroups := make ( map [ string ] * Group )
newGroups [ "default;" ] = newGroup
2022-03-14 06:20:07 -07:00
restoreTime := baseTime . Add ( tst . restoreDuration ) . Add ( evalDelay )
2018-08-02 03:18:24 -07:00
// First eval before restoration.
newGroup . Eval ( suite . Context ( ) , restoreTime )
// Restore happens here.
newGroup . RestoreForState ( restoreTime )
got := newRule . ActiveAlerts ( )
for _ , aa := range got {
2020-10-29 02:43:23 -07:00
require . Zero ( t , aa . Labels . Get ( model . MetricNameLabel ) , "%s label set on active alert: %s" , model . MetricNameLabel , aa . Labels )
2018-08-02 03:18:24 -07:00
}
sort . Slice ( got , func ( i , j int ) bool {
return labels . Compare ( got [ i ] . Labels , got [ j ] . Labels ) < 0
} )
// Checking if we have restored it correctly.
if tst . noRestore {
2020-10-29 02:43:23 -07:00
require . Equal ( t , tst . num , len ( got ) )
2018-08-02 03:18:24 -07:00
for _ , e := range got {
2020-10-29 02:43:23 -07:00
require . Equal ( t , e . ActiveAt , restoreTime )
2018-08-02 03:18:24 -07:00
}
} else if tst . gracePeriod {
2020-10-29 02:43:23 -07:00
require . Equal ( t , tst . num , len ( got ) )
2018-08-02 03:18:24 -07:00
for _ , e := range got {
2020-10-29 02:43:23 -07:00
require . Equal ( t , opts . ForGracePeriod , e . ActiveAt . Add ( alertForDuration ) . Sub ( restoreTime ) )
2018-08-02 03:18:24 -07:00
}
} else {
exp := tst . alerts
2020-10-29 02:43:23 -07:00
require . Equal ( t , len ( exp ) , len ( got ) )
2018-08-16 10:26:15 -07:00
sortAlerts ( exp )
sortAlerts ( got )
2018-08-02 03:18:24 -07:00
for i , e := range exp {
2020-10-29 02:43:23 -07:00
require . Equal ( t , e . Labels , got [ i ] . Labels )
2018-08-02 03:18:24 -07:00
// Difference in time should be within 1e6 ns, i.e. 1ms
// (due to conversion between ns & ms, float64 & int64).
2022-03-14 06:20:07 -07:00
activeAtDiff := evalDelay . Seconds ( ) + float64 ( e . ActiveAt . Unix ( ) + int64 ( tst . downDuration / time . Second ) - got [ i ] . ActiveAt . Unix ( ) )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 0.0 , math . Abs ( activeAtDiff ) , "'for' state restored time is wrong" )
2018-08-02 03:18:24 -07:00
}
}
}
2022-03-14 06:20:07 -07:00
for _ , evalDelay := range [ ] time . Duration { 0 , time . Minute } {
for _ , tst := range tests {
testFunc ( tst , evalDelay )
}
2018-08-02 03:18:24 -07:00
}
// Testing the grace period.
for _ , duration := range [ ] time . Duration { 10 * time . Minute , 15 * time . Minute , 20 * time . Minute } {
evalTime := baseTime . Add ( duration )
group . Eval ( suite . Context ( ) , evalTime )
}
2022-03-14 06:20:07 -07:00
for _ , evalDelay := range [ ] time . Duration { 0 , time . Minute } {
testFunc ( testInput {
restoreDuration : 25 * time . Minute ,
alerts : [ ] * Alert { } ,
gracePeriod : true ,
num : 2 ,
} , evalDelay )
}
2018-08-02 03:18:24 -07:00
}
2017-05-18 09:47:00 -07:00
func TestStaleness ( t * testing . T ) {
2022-03-14 06:20:07 -07:00
for _ , evalDelay := range [ ] time . Duration { 0 , time . Minute } {
st := teststorage . New ( t )
defer st . Close ( )
engineOpts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( engineOpts )
opts := & ManagerOptions {
QueryFunc : EngineQueryFunc ( engine , st ) ,
Appendable : st ,
Queryable : st ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
}
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
expr , err := parser . ParseExpr ( "a + 1" )
require . NoError ( t , err )
rule := NewRecordingRule ( "a_plus_one" , expr , labels . Labels { } )
group := NewGroup ( GroupOptions {
Name : "default" ,
Interval : time . Second ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
EvaluationDelay : & evalDelay ,
} )
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
// A time series that has two samples and then goes stale.
app := st . Appender ( context . Background ( ) )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 0 , 1 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 1000 , 2 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 2000 , math . Float64frombits ( value . StaleNaN ) )
2017-11-11 02:29:47 -08:00
2022-03-14 06:20:07 -07:00
err = app . Commit ( )
require . NoError ( t , err )
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
ctx := context . Background ( )
2017-11-23 23:59:05 -08:00
2022-03-14 06:20:07 -07:00
// Execute 3 times, 1 second apart.
group . Eval ( ctx , time . Unix ( 0 , 0 ) . Add ( evalDelay ) )
group . Eval ( ctx , time . Unix ( 1 , 0 ) . Add ( evalDelay ) )
group . Eval ( ctx , time . Unix ( 2 , 0 ) . Add ( evalDelay ) )
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
querier , err := st . Querier ( context . Background ( ) , 0 , 2000 )
require . NoError ( t , err )
defer querier . Close ( )
2017-11-23 04:50:06 -08:00
2022-03-14 06:20:07 -07:00
matcher , err := labels . NewMatcher ( labels . MatchEqual , model . MetricNameLabel , "a_plus_one" )
require . NoError ( t , err )
2017-11-23 04:50:06 -08:00
2022-03-14 06:20:07 -07:00
set := querier . Select ( false , nil , matcher )
samples , err := readSeriesSet ( set )
require . NoError ( t , err )
2017-11-23 04:50:06 -08:00
2022-03-14 06:20:07 -07:00
metric := labels . FromStrings ( model . MetricNameLabel , "a_plus_one" ) . String ( )
metricSample , ok := samples [ metric ]
2017-11-11 02:29:47 -08:00
2022-03-14 06:20:07 -07:00
require . True ( t , ok , "Series %s not returned." , metric )
require . True ( t , value . IsStaleNaN ( metricSample [ 2 ] . V ) , "Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( metricSample [ 2 ] . V ) )
metricSample [ 2 ] . V = 42 // require.Equal cannot handle NaN.
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
want := map [ string ] [ ] promql . Point {
metric : { { T : 0 , V : 2 } , { T : 1000 , V : 3 } , { T : 2000 , V : 42 } } ,
}
2017-05-18 09:47:00 -07:00
2022-03-14 06:20:07 -07:00
require . Equal ( t , want , samples )
}
2017-05-18 09:47:00 -07:00
}
2020-10-29 02:43:23 -07:00
// Convert a SeriesSet into a form usable with require.Equal.
2017-05-18 09:47:00 -07:00
func readSeriesSet ( ss storage . SeriesSet ) ( map [ string ] [ ] promql . Point , error ) {
result := map [ string ] [ ] promql . Point { }
2022-09-20 10:16:45 -07:00
var it chunkenc . Iterator
2017-05-18 09:47:00 -07:00
for ss . Next ( ) {
series := ss . At ( )
points := [ ] promql . Point { }
2022-09-20 10:16:45 -07:00
it := series . Iterator ( it )
2021-11-28 23:54:23 -08:00
for it . Next ( ) == chunkenc . ValFloat {
2017-05-18 09:47:00 -07:00
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 {
2023-01-09 03:21:38 -08:00
NewAlertingRule ( "alert" , nil , 0 , 0 , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "rule1" , nil , labels . EmptyLabels ( ) ) ,
NewRecordingRule ( "rule2" , nil , labels . EmptyLabels ( ) ) ,
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v1" ) ) ,
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v2" ) ) ,
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v3" ) ) ,
2023-01-09 03:21:38 -08:00
NewAlertingRule ( "alert2" , nil , 0 , 0 , labels . FromStrings ( "l2" , "v1" ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
2017-05-19 08:43:59 -07:00
} ,
seriesInPreviousEval : [ ] map [ string ] labels . Labels {
2019-08-07 08:11:05 -07:00
{ } ,
{ } ,
{ } ,
2022-07-21 09:44:35 -07:00
{ "r3a" : labels . FromStrings ( "l1" , "v1" ) } ,
{ "r3b" : labels . FromStrings ( "l1" , "v2" ) } ,
{ "r3c" : labels . FromStrings ( "l1" , "v3" ) } ,
{ "a2" : labels . FromStrings ( "l2" , "v1" ) } ,
2017-05-19 08:43:59 -07:00
} ,
2020-08-25 03:38:06 -07:00
evaluationTime : time . Second ,
2017-05-19 08:43:59 -07:00
}
oldGroup . rules [ 0 ] . ( * AlertingRule ) . active [ 42 ] = nil
newGroup := & Group {
rules : [ ] Rule {
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v0" ) ) ,
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v1" ) ) ,
NewRecordingRule ( "rule3" , nil , labels . FromStrings ( "l1" , "v2" ) ) ,
2023-01-09 03:21:38 -08:00
NewAlertingRule ( "alert" , nil , 0 , 0 , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "rule1" , nil , labels . EmptyLabels ( ) ) ,
2023-01-09 03:21:38 -08:00
NewAlertingRule ( "alert2" , nil , 0 , 0 , labels . FromStrings ( "l2" , "v0" ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
NewAlertingRule ( "alert2" , nil , 0 , 0 , labels . FromStrings ( "l2" , "v1" ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "rule4" , nil , labels . EmptyLabels ( ) ) ,
2017-05-19 08:43:59 -07:00
} ,
2019-03-15 08:23:36 -07:00
seriesInPreviousEval : make ( [ ] map [ string ] labels . Labels , 8 ) ,
2017-05-19 08:43:59 -07:00
}
2018-07-18 06:14:38 -07:00
newGroup . CopyState ( oldGroup )
2017-05-19 08:43:59 -07:00
want := [ ] map [ string ] labels . Labels {
nil ,
2022-07-21 09:44:35 -07:00
{ "r3a" : labels . FromStrings ( "l1" , "v1" ) } ,
{ "r3b" : labels . FromStrings ( "l1" , "v2" ) } ,
2019-08-07 08:11:05 -07:00
{ } ,
{ } ,
2017-05-19 08:43:59 -07:00
nil ,
2022-07-21 09:44:35 -07:00
{ "a2" : labels . FromStrings ( "l2" , "v1" ) } ,
2019-03-15 08:23:36 -07:00
nil ,
2017-05-19 08:43:59 -07:00
}
2020-10-29 02:43:23 -07:00
require . Equal ( t , want , newGroup . seriesInPreviousEval )
require . Equal ( t , oldGroup . rules [ 0 ] , newGroup . rules [ 3 ] )
require . Equal ( t , oldGroup . evaluationTime , newGroup . evaluationTime )
require . Equal ( t , oldGroup . lastEvaluation , newGroup . lastEvaluation )
2022-07-21 09:44:35 -07:00
require . Equal ( t , [ ] labels . Labels { labels . FromStrings ( "l1" , "v3" ) } , newGroup . staleSeries )
2019-08-07 08:11:05 -07:00
}
func TestDeletedRuleMarkedStale ( t * testing . T ) {
2020-03-12 02:36:09 -07:00
st := teststorage . New ( t )
defer st . Close ( )
2019-08-07 08:11:05 -07:00
oldGroup := & Group {
rules : [ ] Rule {
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "rule1" , nil , labels . FromStrings ( "l1" , "v1" ) ) ,
2019-08-07 08:11:05 -07:00
} ,
seriesInPreviousEval : [ ] map [ string ] labels . Labels {
2022-07-21 09:44:35 -07:00
{ "r1" : labels . FromStrings ( "l1" , "v1" ) } ,
2019-08-07 08:11:05 -07:00
} ,
}
newGroup := & Group {
rules : [ ] Rule { } ,
seriesInPreviousEval : [ ] map [ string ] labels . Labels { } ,
opts : & ManagerOptions {
2020-03-12 02:36:09 -07:00
Appendable : st ,
2019-08-07 08:11:05 -07:00
} ,
}
newGroup . CopyState ( oldGroup )
newGroup . Eval ( context . Background ( ) , time . Unix ( 0 , 0 ) )
2020-03-12 02:36:09 -07:00
querier , err := st . Querier ( context . Background ( ) , 0 , 2000 )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-08-07 08:11:05 -07:00
defer querier . Close ( )
matcher , err := labels . NewMatcher ( labels . MatchEqual , "l1" , "v1" )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-08-07 08:11:05 -07:00
2020-06-09 09:57:31 -07:00
set := querier . Select ( false , nil , matcher )
2019-08-07 08:11:05 -07:00
samples , err := readSeriesSet ( set )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-08-07 08:11:05 -07:00
metric := labels . FromStrings ( "l1" , "v1" ) . String ( )
metricSample , ok := samples [ metric ]
2020-10-29 02:43:23 -07:00
require . True ( t , ok , "Series %s not returned." , metric )
require . True ( t , value . IsStaleNaN ( metricSample [ 0 ] . V ) , "Appended sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( metricSample [ 0 ] . V ) )
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
}
2020-03-12 02:36:09 -07:00
st := teststorage . New ( t )
defer st . Close ( )
2018-10-02 04:59:19 -07:00
opts := promql . EngineOpts {
2020-01-28 12:38:49 -08:00
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
2018-10-02 04:59:19 -07:00
}
engine := promql . NewEngine ( opts )
2017-11-01 04:58:00 -07:00
ruleManager := NewManager ( & ManagerOptions {
2021-11-26 05:03:40 -08:00
Appendable : st ,
Queryable : st ,
QueryFunc : EngineQueryFunc ( engine , st ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
2017-11-01 04:58:00 -07:00
} )
2020-07-21 15:13:24 -07:00
ruleManager . start ( )
2018-08-20 05:51:05 -07:00
defer ruleManager . Stop ( )
2017-11-01 04:58:00 -07:00
2022-07-21 09:44:35 -07:00
err := ruleManager . Update ( 10 * time . Second , files , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
require . Greater ( t , len ( ruleManager . groups ) , 0 , "expected non-empty rule groups" )
2019-12-19 02:41:11 -08:00
ogs := map [ string ] * Group { }
for h , g := range ruleManager . groups {
2017-11-01 04:58:00 -07:00
g . seriesInPreviousEval = [ ] map [ string ] labels . Labels {
expected ,
}
2019-12-19 02:41:11 -08:00
ogs [ h ] = g
2017-11-01 04:58:00 -07:00
}
2022-07-21 09:44:35 -07:00
err = ruleManager . Update ( 10 * time . Second , files , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-12-19 02:41:11 -08:00
for h , g := range ruleManager . groups {
2017-11-01 04:58:00 -07:00
for _ , actual := range g . seriesInPreviousEval {
2020-10-29 02:43:23 -07:00
require . Equal ( t , expected , actual )
2017-11-01 04:58:00 -07:00
}
2019-12-19 02:41:11 -08:00
// Groups are the same because of no updates.
2020-10-29 02:43:23 -07:00
require . Equal ( t , ogs [ h ] , g )
2019-12-19 02:41:11 -08:00
}
// Groups will be recreated if updated.
rgs , errs := rulefmt . ParseFile ( "fixtures/rules.yaml" )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 0 , len ( errs ) , "file parsing failures" )
2019-12-19 02:41:11 -08:00
2022-04-27 02:24:36 -07:00
tmpFile , err := os . CreateTemp ( "" , "rules.test.*.yaml" )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-12-19 02:41:11 -08:00
defer os . Remove ( tmpFile . Name ( ) )
defer tmpFile . Close ( )
2022-07-21 09:44:35 -07:00
err = ruleManager . Update ( 10 * time . Second , [ ] string { tmpFile . Name ( ) } , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2019-12-19 02:41:11 -08:00
for h , g := range ruleManager . groups {
ogs [ h ] = g
}
2019-12-19 02:46:22 -08:00
// Update interval and reload.
2019-12-19 02:41:11 -08:00
for i , g := range rgs . Groups {
if g . Interval != 0 {
rgs . Groups [ i ] . Interval = g . Interval * 2
} else {
rgs . Groups [ i ] . Interval = model . Duration ( 10 )
}
}
reloadAndValidate ( rgs , t , tmpFile , ruleManager , expected , ogs )
2021-09-15 00:48:26 -07:00
// Update limit and reload.
for i := range rgs . Groups {
rgs . Groups [ i ] . Limit = 1
}
reloadAndValidate ( rgs , t , tmpFile , ruleManager , expected , ogs )
2019-12-19 02:46:22 -08:00
// Change group rules and reload.
2019-12-19 02:41:11 -08:00
for i , g := range rgs . Groups {
for j , r := range g . Rules {
2020-01-15 10:07:54 -08:00
rgs . Groups [ i ] . Rules [ j ] . Expr . SetString ( fmt . Sprintf ( "%s * 0" , r . Expr . Value ) )
2019-12-19 02:41:11 -08:00
}
}
reloadAndValidate ( rgs , t , tmpFile , ruleManager , expected , ogs )
2021-11-25 04:44:29 -08:00
// Change group source tenants and reload.
for i := range rgs . Groups {
rgs . Groups [ i ] . SourceTenants = [ ] string { "tenant-2" }
}
reloadAndValidate ( rgs , t , tmpFile , ruleManager , expected , ogs )
2019-12-19 02:41:11 -08:00
}
2022-09-09 13:30:03 -07:00
func TestUpdate_AlwaysRestore ( t * testing . T ) {
st := teststorage . New ( t )
defer st . Close ( )
ruleManager := NewManager ( & ManagerOptions {
Appendable : st ,
Queryable : st ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
AlwaysRestoreAlertState : true ,
} )
ruleManager . start ( )
defer ruleManager . Stop ( )
err := ruleManager . Update ( 10 * time . Second , [ ] string { "fixtures/rules_alerts.yaml" } , nil , "" , nil )
require . NoError ( t , err )
for _ , g := range ruleManager . groups {
require . True ( t , g . shouldRestore )
g . shouldRestore = false // set to false to check if Update will set it to true again
}
// Use different file, so groups haven't changed, therefore, we expect state restoration
err = ruleManager . Update ( 10 * time . Second , [ ] string { "fixtures/rules_alerts2.yaml" } , nil , "" , nil )
for _ , g := range ruleManager . groups {
require . True ( t , g . shouldRestore )
}
require . NoError ( t , err )
}
func TestUpdate_AlwaysRestoreDoesntAffectUnchangedGroups ( t * testing . T ) {
files := [ ] string { "fixtures/rules_alerts.yaml" }
st := teststorage . New ( t )
defer st . Close ( )
ruleManager := NewManager ( & ManagerOptions {
Appendable : st ,
Queryable : st ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
AlwaysRestoreAlertState : true ,
} )
ruleManager . start ( )
defer ruleManager . Stop ( )
err := ruleManager . Update ( 10 * time . Second , files , nil , "" , nil )
require . NoError ( t , err )
for _ , g := range ruleManager . groups {
require . True ( t , g . shouldRestore )
g . shouldRestore = false // set to false to check if Update will set it to true again
}
// Use the same file, so groups haven't changed, therefore, we don't expect state restoration
err = ruleManager . Update ( 10 * time . Second , files , nil , "" , nil )
for _ , g := range ruleManager . groups {
require . False ( t , g . shouldRestore )
}
require . NoError ( t , err )
}
2021-11-29 04:53:23 -08:00
func TestUpdateSetsSourceTenants ( t * testing . T ) {
st := teststorage . New ( t )
defer st . Close ( )
opts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( opts )
ruleManager := NewManager ( & ManagerOptions {
Appendable : st ,
Queryable : st ,
QueryFunc : EngineQueryFunc ( engine , st ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
} )
ruleManager . start ( )
defer ruleManager . Stop ( )
rgs , errs := rulefmt . ParseFile ( "fixtures/rules_with_source_tenants.yaml" )
2021-11-29 09:30:43 -08:00
require . Empty ( t , errs , "file parsing failures" )
2021-11-29 04:53:23 -08:00
2022-06-09 02:49:45 -07:00
tmpFile , err := os . CreateTemp ( "" , "rules.test.*.yaml" )
2021-11-29 04:53:23 -08:00
require . NoError ( t , err )
defer os . Remove ( tmpFile . Name ( ) )
defer tmpFile . Close ( )
2021-11-29 09:30:43 -08:00
reloadRules ( rgs , t , tmpFile , ruleManager , 0 )
2021-11-29 04:53:23 -08:00
// check that all source tenants were actually set
require . Len ( t , ruleManager . groups , len ( rgs . Groups ) )
for _ , expectedGroup := range rgs . Groups {
actualGroup , ok := ruleManager . groups [ GroupKey ( tmpFile . Name ( ) , expectedGroup . Name ) ]
require . True ( t , ok , "actual groups don't contain at one of the expected groups" )
require . ElementsMatch ( t , expectedGroup . SourceTenants , actualGroup . SourceTenants ( ) )
}
}
2023-01-19 05:51:26 -08:00
func TestAlignEvaluationTimeOnInterval ( t * testing . T ) {
st := teststorage . New ( t )
defer st . Close ( )
opts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( opts )
ruleManager := NewManager ( & ManagerOptions {
Appendable : st ,
Queryable : st ,
QueryFunc : EngineQueryFunc ( engine , st ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
} )
ruleManager . start ( )
defer ruleManager . Stop ( )
rgs , errs := rulefmt . ParseFile ( "fixtures/rules_with_alignment.yaml" )
require . Empty ( t , errs , "file parsing failures" )
tmpFile , err := os . CreateTemp ( "" , "rules.test.*.yaml" )
require . NoError ( t , err )
defer os . Remove ( tmpFile . Name ( ) )
defer tmpFile . Close ( )
reloadRules ( rgs , t , tmpFile , ruleManager , 0 )
// Verify that all groups are loaded, and let's check their evaluation times.
loadedGroups := ruleManager . RuleGroups ( )
require . Len ( t , loadedGroups , len ( rgs . Groups ) )
assertGroupEvalTimeAlignedOnIntervalIsHonored := func ( groupName string , expectedAligned bool ) {
g := ( * Group ) ( nil )
for _ , lg := range loadedGroups {
if lg . name == groupName {
g = lg
break
}
}
require . NotNil ( t , g , "group not found: %s" , groupName )
// When "g.hash() % g.interval == 0" alignment cannot be checked, because aligned and unaligned eval timestamps
// would be the same. This can happen because g.hash() depends on path passed to ruleManager.Update function,
// and this test uses temporary directory for storing rule group files.
if g . hash ( ) % uint64 ( g . interval ) == 0 {
t . Skip ( "skipping test, because rule group hash is divisible by interval, which makes eval timestamp always aligned to the interval" )
}
now := time . Now ( )
ts := g . EvalTimestamp ( now . UnixNano ( ) )
aligned := ts . UnixNano ( ) % g . interval . Nanoseconds ( ) == 0
require . Equal ( t , expectedAligned , aligned , "group: %s, hash: %d, now: %d" , groupName , g . hash ( ) , now . UnixNano ( ) )
}
assertGroupEvalTimeAlignedOnIntervalIsHonored ( "aligned" , true )
assertGroupEvalTimeAlignedOnIntervalIsHonored ( "aligned_with_crazy_interval" , true )
assertGroupEvalTimeAlignedOnIntervalIsHonored ( "unaligned_default" , false )
assertGroupEvalTimeAlignedOnIntervalIsHonored ( "unaligned_explicit" , false )
}
2021-11-29 09:30:43 -08:00
func TestGroupEvaluationContextFuncIsCalledWhenSupplied ( t * testing . T ) {
type testContextKeyType string
var testContextKey testContextKeyType = "TestGroupEvaluationContextFuncIsCalledWhenSupplied"
oldContextTestValue := context . Background ( ) . Value ( testContextKey )
contextTestValueChannel := make ( chan interface { } )
mockQueryFunc := func ( ctx context . Context , qs string , t time . Time ) ( promql . Vector , error ) {
contextTestValueChannel <- ctx . Value ( testContextKey )
return promql . Vector { } , nil
}
mockContextWrapFunc := func ( ctx context . Context , g * Group ) context . Context {
return context . WithValue ( ctx , testContextKey , 42 )
}
st := teststorage . New ( t )
defer st . Close ( )
ruleManager := NewManager ( & ManagerOptions {
Appendable : st ,
Queryable : st ,
QueryFunc : mockQueryFunc ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
GroupEvaluationContextFunc : mockContextWrapFunc ,
} )
rgs , errs := rulefmt . ParseFile ( "fixtures/rules_with_source_tenants.yaml" )
require . Empty ( t , errs , "file parsing failures" )
2022-06-09 02:49:45 -07:00
tmpFile , err := os . CreateTemp ( "" , "rules.test.*.yaml" )
2021-11-29 09:30:43 -08:00
require . NoError ( t , err )
defer os . Remove ( tmpFile . Name ( ) )
defer tmpFile . Close ( )
// no filesystem is harmed when running this test, set the interval low
reloadRules ( rgs , t , tmpFile , ruleManager , 10 * time . Millisecond )
ruleManager . start ( )
defer ruleManager . Stop ( )
// check that all source tenants were actually set
require . Len ( t , ruleManager . groups , len ( rgs . Groups ) )
require . Nil ( t , oldContextTestValue , "Context contained test key before the test, impossible" )
newContextTestValue := <- contextTestValueChannel
require . Equal ( t , 42 , newContextTestValue , "Context does not contain the correct value that should be injected" )
}
2020-01-15 10:07:54 -08:00
// ruleGroupsTest for running tests over rules.
type ruleGroupsTest struct {
Groups [ ] ruleGroupTest ` yaml:"groups" `
}
// ruleGroupTest forms a testing struct for running tests over rules.
type ruleGroupTest struct {
2023-01-19 07:11:44 -08:00
Name string ` yaml:"name" `
Interval model . Duration ` yaml:"interval,omitempty" `
Limit int ` yaml:"limit,omitempty" `
Rules [ ] rulefmt . Rule ` yaml:"rules" `
SourceTenants [ ] string ` yaml:"source_tenants,omitempty" `
AlignEvaluationTimeOnInterval bool ` yaml:"align_evaluation_time_on_interval,omitempty" `
2020-01-15 10:07:54 -08:00
}
func formatRules ( r * rulefmt . RuleGroups ) ruleGroupsTest {
grps := r . Groups
tmp := [ ] ruleGroupTest { }
for _ , g := range grps {
rtmp := [ ] rulefmt . Rule { }
for _ , r := range g . Rules {
rtmp = append ( rtmp , rulefmt . Rule {
Record : r . Record . Value ,
Alert : r . Alert . Value ,
Expr : r . Expr . Value ,
For : r . For ,
Labels : r . Labels ,
Annotations : r . Annotations ,
} )
}
tmp = append ( tmp , ruleGroupTest {
2023-01-19 07:11:44 -08:00
Name : g . Name ,
Interval : g . Interval ,
Limit : g . Limit ,
Rules : rtmp ,
SourceTenants : g . SourceTenants ,
AlignEvaluationTimeOnInterval : g . AlignEvaluationTimeOnInterval ,
2020-01-15 10:07:54 -08:00
} )
}
return ruleGroupsTest {
Groups : tmp ,
}
}
2021-11-29 09:30:43 -08:00
func reloadRules ( rgs * rulefmt . RuleGroups , t * testing . T , tmpFile * os . File , ruleManager * Manager , interval time . Duration ) {
if interval == 0 {
interval = 10 * time . Second
}
2020-01-15 10:07:54 -08:00
bs , err := yaml . Marshal ( formatRules ( rgs ) )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2021-11-29 04:53:23 -08:00
_ , _ = tmpFile . Seek ( 0 , 0 )
2019-12-19 02:41:11 -08:00
_ , err = tmpFile . Write ( bs )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2022-04-12 07:40:00 -07:00
err = ruleManager . Update ( interval , [ ] string { tmpFile . Name ( ) } , nil , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2021-11-29 04:53:23 -08:00
}
func reloadAndValidate ( rgs * rulefmt . RuleGroups , t * testing . T , tmpFile * os . File , ruleManager * Manager , expected map [ string ] labels . Labels , ogs map [ string ] * Group ) {
2021-11-29 09:30:43 -08:00
reloadRules ( rgs , t , tmpFile , ruleManager , 0 )
2019-12-19 02:41:11 -08:00
for h , g := range ruleManager . groups {
if ogs [ h ] == g {
t . Fail ( )
}
ogs [ h ] = g
2017-11-01 04:58:00 -07:00
}
}
2018-08-27 09:41:42 -07:00
func TestNotify ( t * testing . T ) {
2019-08-08 18:35:39 -07:00
storage := teststorage . New ( t )
2018-08-27 09:41:42 -07:00
defer storage . Close ( )
2018-10-02 04:59:19 -07:00
engineOpts := promql . EngineOpts {
2020-01-28 12:38:49 -08:00
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
2018-10-02 04:59:19 -07:00
}
engine := promql . NewEngine ( engineOpts )
2018-08-27 09:41:42 -07:00
var lastNotified [ ] * Alert
notifyFunc := func ( ctx context . Context , expr string , alerts ... * Alert ) {
lastNotified = alerts
}
opts := & ManagerOptions {
QueryFunc : EngineQueryFunc ( engine , storage ) ,
Appendable : storage ,
2020-06-26 11:06:36 -07:00
Queryable : storage ,
2018-08-27 09:41:42 -07:00
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
NotifyFunc : notifyFunc ,
ResendDelay : 2 * time . Second ,
}
2020-02-03 10:23:07 -08:00
expr , err := parser . ParseExpr ( "a > 1" )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2023-01-09 03:21:38 -08:00
rule := NewAlertingRule ( "aTooHigh" , expr , 0 , 0 , labels . Labels { } , labels . Labels { } , labels . EmptyLabels ( ) , "" , true , log . NewNopLogger ( ) )
2020-02-12 07:22:18 -08:00
group := NewGroup ( GroupOptions {
Name : "alert" ,
Interval : time . Second ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
} )
2018-08-27 09:41:42 -07:00
2020-07-24 07:10:51 -07:00
app := storage . Appender ( context . Background ( ) )
2021-02-18 04:07:00 -08:00
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 1000 , 2 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 2000 , 3 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 5000 , 3 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 6000 , 0 )
2018-08-27 09:41:42 -07:00
err = app . Commit ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2018-08-27 09:41:42 -07:00
ctx := context . Background ( )
// Alert sent right away
group . Eval ( ctx , time . Unix ( 1 , 0 ) )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 1 , len ( lastNotified ) )
require . NotZero ( t , lastNotified [ 0 ] . ValidUntil , "ValidUntil should not be zero" )
2018-08-27 09:41:42 -07:00
// Alert is not sent 1s later
group . Eval ( ctx , time . Unix ( 2 , 0 ) )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 0 , len ( lastNotified ) )
2018-08-27 09:41:42 -07:00
// Alert is resent at t=5s
group . Eval ( ctx , time . Unix ( 5 , 0 ) )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 1 , len ( lastNotified ) )
2018-08-27 09:41:42 -07:00
// Resolution alert sent right away
group . Eval ( ctx , time . Unix ( 6 , 0 ) )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 1 , len ( lastNotified ) )
2018-08-27 09:41:42 -07:00
}
2020-01-27 04:41:32 -08:00
func TestMetricsUpdate ( t * testing . T ) {
files := [ ] string { "fixtures/rules.yaml" , "fixtures/rules2.yaml" }
metricNames := [ ] string {
2020-04-08 14:21:37 -07:00
"prometheus_rule_evaluations_total" ,
"prometheus_rule_evaluation_failures_total" ,
2020-01-27 04:41:32 -08:00
"prometheus_rule_group_interval_seconds" ,
"prometheus_rule_group_last_duration_seconds" ,
"prometheus_rule_group_last_evaluation_timestamp_seconds" ,
"prometheus_rule_group_rules" ,
}
storage := teststorage . New ( t )
defer storage . Close ( )
2020-07-21 15:13:24 -07:00
registry := prometheus . NewRegistry ( )
2020-01-27 04:41:32 -08:00
opts := promql . EngineOpts {
2020-01-28 12:38:49 -08:00
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
2020-01-27 04:41:32 -08:00
}
engine := promql . NewEngine ( opts )
ruleManager := NewManager ( & ManagerOptions {
Appendable : storage ,
2020-06-26 11:06:36 -07:00
Queryable : storage ,
2020-01-27 04:41:32 -08:00
QueryFunc : EngineQueryFunc ( engine , storage ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
Registerer : registry ,
} )
2020-07-21 15:13:24 -07:00
ruleManager . start ( )
2020-01-27 04:41:32 -08:00
defer ruleManager . Stop ( )
countMetrics := func ( ) int {
ms , err := registry . Gather ( )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-01-27 04:41:32 -08:00
var metrics int
for _ , m := range ms {
s := m . GetName ( )
for _ , n := range metricNames {
if s == n {
metrics += len ( m . Metric )
break
}
}
}
return metrics
}
cases := [ ] struct {
files [ ] string
metrics int
} {
{
files : files ,
2020-04-08 14:21:37 -07:00
metrics : 12 ,
2020-01-27 04:41:32 -08:00
} ,
{
files : files [ : 1 ] ,
2020-04-08 14:21:37 -07:00
metrics : 6 ,
2020-01-27 04:41:32 -08:00
} ,
{
files : files [ : 0 ] ,
metrics : 0 ,
} ,
{
files : files [ 1 : ] ,
2020-04-08 14:21:37 -07:00
metrics : 6 ,
2020-01-27 04:41:32 -08:00
} ,
}
for i , c := range cases {
2022-07-21 09:44:35 -07:00
err := ruleManager . Update ( time . Second , c . files , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-01-27 04:41:32 -08:00
time . Sleep ( 2 * time . Second )
2020-10-29 02:43:23 -07:00
require . Equal ( t , c . metrics , countMetrics ( ) , "test %d: invalid count of metrics" , i )
2020-01-27 04:41:32 -08:00
}
}
2020-02-12 07:22:18 -08:00
func TestGroupStalenessOnRemoval ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping test in short mode." )
}
files := [ ] string { "fixtures/rules2.yaml" }
sameFiles := [ ] string { "fixtures/rules2_copy.yaml" }
storage := teststorage . New ( t )
defer storage . Close ( )
opts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( opts )
ruleManager := NewManager ( & ManagerOptions {
Appendable : storage ,
2020-06-26 11:06:36 -07:00
Queryable : storage ,
2020-02-12 07:22:18 -08:00
QueryFunc : EngineQueryFunc ( engine , storage ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
} )
var stopped bool
2020-07-21 15:13:24 -07:00
ruleManager . start ( )
2020-02-12 07:22:18 -08:00
defer func ( ) {
if ! stopped {
ruleManager . Stop ( )
}
} ( )
cases := [ ] struct {
files [ ] string
staleNaN int
} {
{
files : files ,
staleNaN : 0 ,
} ,
{
// When we remove the files, it should produce a staleness marker.
files : files [ : 0 ] ,
staleNaN : 1 ,
} ,
{
// Rules that produce the same metrics but in a different file
// should not produce staleness marker.
files : sameFiles ,
staleNaN : 0 ,
} ,
{
// Staleness marker should be present as we don't have any rules
// loaded anymore.
files : files [ : 0 ] ,
staleNaN : 1 ,
} ,
{
// Add rules back so we have rules loaded when we stop the manager
// and check for the absence of staleness markers.
files : sameFiles ,
staleNaN : 0 ,
} ,
}
var totalStaleNaN int
for i , c := range cases {
2022-07-21 09:44:35 -07:00
err := ruleManager . Update ( time . Second , c . files , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
time . Sleep ( 3 * time . Second )
totalStaleNaN += c . staleNaN
2020-10-29 02:43:23 -07:00
require . Equal ( t , totalStaleNaN , countStaleNaN ( t , storage ) , "test %d/%q: invalid count of staleness markers" , i , c . files )
2020-02-12 07:22:18 -08:00
}
ruleManager . Stop ( )
stopped = true
2020-10-29 02:43:23 -07:00
require . Equal ( t , totalStaleNaN , countStaleNaN ( t , storage ) , "invalid count of staleness markers after stopping the engine" )
2020-02-12 07:22:18 -08:00
}
func TestMetricsStalenessOnManagerShutdown ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping test in short mode." )
}
files := [ ] string { "fixtures/rules2.yaml" }
storage := teststorage . New ( t )
defer storage . Close ( )
opts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( opts )
ruleManager := NewManager ( & ManagerOptions {
Appendable : storage ,
2020-06-26 11:06:36 -07:00
Queryable : storage ,
2020-02-12 07:22:18 -08:00
QueryFunc : EngineQueryFunc ( engine , storage ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
} )
var stopped bool
2020-07-21 15:13:24 -07:00
ruleManager . start ( )
2020-02-12 07:22:18 -08:00
defer func ( ) {
if ! stopped {
ruleManager . Stop ( )
}
} ( )
2022-07-21 09:44:35 -07:00
err := ruleManager . Update ( 2 * time . Second , files , labels . EmptyLabels ( ) , "" , nil )
2020-02-12 07:22:18 -08:00
time . Sleep ( 4 * time . Second )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
start := time . Now ( )
2022-07-21 09:44:35 -07:00
err = ruleManager . Update ( 3 * time . Second , files [ : 0 ] , labels . EmptyLabels ( ) , "" , nil )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
ruleManager . Stop ( )
stopped = true
2020-10-29 02:43:23 -07:00
require . True ( t , time . Since ( start ) < 1 * time . Second , "rule manager does not stop early" )
2020-02-12 07:22:18 -08:00
time . Sleep ( 5 * time . Second )
2020-10-29 02:43:23 -07:00
require . Equal ( t , 0 , countStaleNaN ( t , storage ) , "invalid count of staleness markers after stopping the engine" )
2020-02-12 07:22:18 -08:00
}
2020-03-12 02:36:09 -07:00
func countStaleNaN ( t * testing . T , st storage . Storage ) int {
2020-02-12 07:22:18 -08:00
var c int
2020-03-12 02:36:09 -07:00
querier , err := st . Querier ( context . Background ( ) , 0 , time . Now ( ) . Unix ( ) * 1000 )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
defer querier . Close ( )
matcher , err := labels . NewMatcher ( labels . MatchEqual , model . MetricNameLabel , "test_2" )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
2020-06-09 09:57:31 -07:00
set := querier . Select ( false , nil , matcher )
2020-02-12 07:22:18 -08:00
samples , err := readSeriesSet ( set )
2020-10-29 02:43:23 -07:00
require . NoError ( t , err )
2020-02-12 07:22:18 -08:00
metric := labels . FromStrings ( model . MetricNameLabel , "test_2" ) . String ( )
metricSample , ok := samples [ metric ]
2020-10-29 02:43:23 -07:00
require . True ( t , ok , "Series %s not returned." , metric )
2020-02-12 07:22:18 -08:00
for _ , s := range metricSample {
if value . IsStaleNaN ( s . V ) {
c ++
}
}
return c
}
2020-07-06 02:35:16 -07:00
func TestGroupHasAlertingRules ( t * testing . T ) {
tests := [ ] struct {
group * Group
want bool
} {
{
group : & Group {
name : "HasAlertingRule" ,
rules : [ ] Rule {
2023-01-09 03:21:38 -08:00
NewAlertingRule ( "alert" , nil , 0 , 0 , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , labels . EmptyLabels ( ) , "" , true , nil ) ,
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "record" , nil , labels . EmptyLabels ( ) ) ,
2020-07-06 02:35:16 -07:00
} ,
} ,
want : true ,
} ,
{
group : & Group {
name : "HasNoRule" ,
rules : [ ] Rule { } ,
} ,
want : false ,
} ,
{
group : & Group {
name : "HasOnlyRecordingRule" ,
rules : [ ] Rule {
2022-07-21 09:44:35 -07:00
NewRecordingRule ( "record" , nil , labels . EmptyLabels ( ) ) ,
2020-07-06 02:35:16 -07:00
} ,
} ,
want : false ,
} ,
}
for i , test := range tests {
got := test . group . HasAlertingRules ( )
2020-10-29 02:43:23 -07:00
require . Equal ( t , test . want , got , "test case %d failed, expected:%t got:%t" , i , test . want , got )
2020-07-06 02:35:16 -07:00
}
}
2021-03-18 07:44:33 -07:00
func TestRuleHealthUpdates ( t * testing . T ) {
st := teststorage . New ( t )
defer st . Close ( )
engineOpts := promql . EngineOpts {
Logger : nil ,
Reg : nil ,
MaxSamples : 10 ,
Timeout : 10 * time . Second ,
}
engine := promql . NewEngine ( engineOpts )
opts := & ManagerOptions {
QueryFunc : EngineQueryFunc ( engine , st ) ,
Appendable : st ,
Queryable : st ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
}
expr , err := parser . ParseExpr ( "a + 1" )
require . NoError ( t , err )
rule := NewRecordingRule ( "a_plus_one" , expr , labels . Labels { } )
group := NewGroup ( GroupOptions {
Name : "default" ,
Interval : time . Second ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
} )
// A time series that has two samples.
app := st . Appender ( context . Background ( ) )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 0 , 1 )
app . Append ( 0 , labels . FromStrings ( model . MetricNameLabel , "a" ) , 1000 , 2 )
err = app . Commit ( )
require . NoError ( t , err )
ctx := context . Background ( )
rules := group . Rules ( ) [ 0 ]
require . NoError ( t , rules . LastError ( ) )
require . Equal ( t , HealthUnknown , rules . Health ( ) )
// Execute 2 times, it should be all green.
group . Eval ( ctx , time . Unix ( 0 , 0 ) )
group . Eval ( ctx , time . Unix ( 1 , 0 ) )
rules = group . Rules ( ) [ 0 ]
require . NoError ( t , rules . LastError ( ) )
require . Equal ( t , HealthGood , rules . Health ( ) )
// Now execute the rule in the past again, this should cause append failures.
group . Eval ( ctx , time . Unix ( 0 , 0 ) )
rules = group . Rules ( ) [ 0 ]
require . EqualError ( t , rules . LastError ( ) , storage . ErrOutOfOrderSample . Error ( ) )
require . Equal ( t , HealthBad , rules . Health ( ) )
}
2022-01-21 06:07:20 -08:00
func TestGroup_Equals ( t * testing . T ) {
testExpression , err := parser . ParseExpr ( "up" )
require . NoError ( t , err )
tests := [ ] struct {
name string
2022-01-21 06:12:45 -08:00
groupOne * Group
groupTwo * Group
2022-01-21 06:07:20 -08:00
areEqual bool
} {
{
name : "identical configs" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
areEqual : true ,
} ,
{
name : "differently ordered source tenants (should still be equivalent)" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-2" , "tenant-1" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-1" , "tenant-2" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
areEqual : true ,
} ,
{
name : "different rule length" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
areEqual : false ,
} ,
{
name : "different rule labels" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "1" : "2" , "3" : "4" } ) ,
} ,
} ,
} ,
areEqual : false ,
} ,
{
name : "different source tenants" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-1" , "tenant-3" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-1" , "tenant-2" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
areEqual : false ,
} ,
{
name : "repeating source tenants" ,
2022-01-21 06:12:45 -08:00
groupOne : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-1" , "tenant-2" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
2022-01-21 06:12:45 -08:00
groupTwo : & Group {
2022-01-21 06:07:20 -08:00
name : "example_group" ,
sourceTenants : [ ] string { "tenant-1" , "tenant-1" } ,
rules : [ ] Rule {
& RecordingRule {
name : "one" ,
vector : testExpression ,
labels : labels . FromMap ( map [ string ] string { "a" : "b" , "c" : "d" } ) ,
} ,
} ,
} ,
areEqual : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2022-01-21 06:12:45 -08:00
require . Equal ( t , tt . areEqual , tt . groupOne . Equals ( tt . groupTwo ) )
require . Equal ( t , tt . areEqual , tt . groupTwo . Equals ( tt . groupOne ) )
2022-01-21 06:07:20 -08:00
} )
}
}
2022-03-14 06:20:07 -07:00
func TestGroup_EvaluationDelay ( t * testing . T ) {
config := `
groups :
- name : group1
evaluation_delay : 2 m
- name : group2
evaluation_delay : 0 s
- name : group3
`
dir := t . TempDir ( )
fname := path . Join ( dir , "rules.yaml" )
2022-06-09 02:49:45 -07:00
err := os . WriteFile ( fname , [ ] byte ( config ) , fs . ModePerm )
2022-03-14 06:20:07 -07:00
require . NoError ( t , err )
m := NewManager ( & ManagerOptions {
Logger : log . NewNopLogger ( ) ,
DefaultEvaluationDelay : func ( ) time . Duration {
return time . Minute
} ,
} )
m . start ( )
2022-04-12 07:40:00 -07:00
err = m . Update ( time . Second , [ ] string { fname } , nil , "" , nil )
2022-03-14 06:20:07 -07:00
require . NoError ( t , err )
rgs := m . RuleGroups ( )
sort . Slice ( rgs , func ( i , j int ) bool {
return rgs [ i ] . Name ( ) < rgs [ j ] . Name ( )
} )
// From config.
require . Equal ( t , 2 * time . Minute , rgs [ 0 ] . EvaluationDelay ( ) )
// Setting 0 in config is detected.
require . Equal ( t , time . Duration ( 0 ) , rgs [ 1 ] . EvaluationDelay ( ) )
// Default when nothing is set.
require . Equal ( t , time . Minute , rgs [ 2 ] . EvaluationDelay ( ) )
m . Stop ( )
}
2022-04-12 07:40:00 -07:00
2022-03-28 17:16:46 -07:00
func TestUpdateMissedEvalMetrics ( t * testing . T ) {
suite , err := promql . NewTest ( t , `
load 5 m
http_requests { instance = "0" } 75 85 50 0 0 25 0 0 40 0 120
` )
require . NoError ( t , err )
defer suite . Close ( )
err = suite . Run ( )
require . NoError ( t , err )
expr , err := parser . ParseExpr ( ` http_requests { group="canary", job="app-server"} < 100 ` )
require . NoError ( t , err )
testValue := 1
overrideFunc := func ( g * Group , lastEvalTimestamp time . Time , log log . Logger ) error {
testValue = 2
return nil
}
type testInput struct {
overrideFunc func ( g * Group , lastEvalTimestamp time . Time , logger log . Logger ) error
expectedValue int
}
tests := [ ] testInput {
// testValue should still have value of 1 since overrideFunc is nil.
{
overrideFunc : nil ,
expectedValue : 1 ,
} ,
// testValue should be incremented to 2 since overrideFunc is called.
{
overrideFunc : overrideFunc ,
expectedValue : 2 ,
} ,
}
testFunc := func ( tst testInput ) {
opts := & ManagerOptions {
QueryFunc : EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) ,
Appendable : suite . Storage ( ) ,
Queryable : suite . Storage ( ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
NotifyFunc : func ( ctx context . Context , expr string , alerts ... * Alert ) { } ,
OutageTolerance : 30 * time . Minute ,
ForGracePeriod : 10 * time . Minute ,
}
activeAlert := & Alert {
State : StateFiring ,
ActiveAt : time . Now ( ) ,
}
m := map [ uint64 ] * Alert { }
m [ 1 ] = activeAlert
rule := & AlertingRule {
2022-07-19 03:58:37 -07:00
name : "HTTPRequestRateLow" ,
vector : expr ,
holdDuration : 5 * time . Minute ,
labels : labels . FromStrings ( "severity" , "critical" ) ,
2022-07-21 09:44:35 -07:00
annotations : labels . EmptyLabels ( ) ,
2022-07-19 03:58:37 -07:00
externalLabels : nil ,
externalURL : "" ,
active : m ,
logger : nil ,
restored : atomic . NewBool ( true ) ,
health : atomic . NewString ( string ( HealthUnknown ) ) ,
evaluationTimestamp : atomic . NewTime ( time . Time { } ) ,
evaluationDuration : atomic . NewDuration ( 0 ) ,
lastError : atomic . NewError ( nil ) ,
2022-03-28 17:16:46 -07:00
}
group := NewGroup ( GroupOptions {
Name : "default" ,
Interval : time . Second ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
RuleGroupPostProcessFunc : tst . overrideFunc ,
} )
go func ( ) {
group . run ( opts . Context )
} ( )
time . Sleep ( 3 * time . Second )
group . stop ( )
require . Equal ( t , tst . expectedValue , testValue )
}
for _ , tst := range tests {
testFunc ( tst )
}
}
2023-01-11 04:48:18 -08:00
func TestNativeHistogramsInRecordingRules ( t * testing . T ) {
suite , err := promql . NewTest ( t , "" )
require . NoError ( t , err )
t . Cleanup ( suite . Close )
err = suite . Run ( )
require . NoError ( t , err )
// Add some histograms.
db := suite . TSDB ( )
2023-02-10 03:39:33 -08:00
hists := tsdbutil . GenerateTestHistograms ( 5 )
2023-01-11 04:48:18 -08:00
ts := time . Now ( )
app := db . Appender ( context . Background ( ) )
for i , h := range hists {
l := labels . FromStrings ( "__name__" , "histogram_metric" , "idx" , fmt . Sprintf ( "%d" , i ) )
_ , err := app . AppendHistogram ( 0 , l , ts . UnixMilli ( ) , h . Copy ( ) , nil )
require . NoError ( t , err )
}
require . NoError ( t , app . Commit ( ) )
opts := & ManagerOptions {
QueryFunc : EngineQueryFunc ( suite . QueryEngine ( ) , suite . Storage ( ) ) ,
Appendable : suite . Storage ( ) ,
Queryable : suite . Storage ( ) ,
Context : context . Background ( ) ,
Logger : log . NewNopLogger ( ) ,
}
expr , err := parser . ParseExpr ( "sum(histogram_metric)" )
require . NoError ( t , err )
rule := NewRecordingRule ( "sum:histogram_metric" , expr , labels . Labels { } )
group := NewGroup ( GroupOptions {
Name : "default" ,
Interval : time . Hour ,
Rules : [ ] Rule { rule } ,
ShouldRestore : true ,
Opts : opts ,
} )
group . Eval ( context . Background ( ) , ts . Add ( 10 * time . Second ) )
q , err := db . Querier ( context . Background ( ) , ts . UnixMilli ( ) , ts . Add ( 20 * time . Second ) . UnixMilli ( ) )
require . NoError ( t , err )
ss := q . Select ( false , nil , labels . MustNewMatcher ( labels . MatchEqual , "__name__" , "sum:histogram_metric" ) )
require . True ( t , ss . Next ( ) )
s := ss . At ( )
require . False ( t , ss . Next ( ) )
require . Equal ( t , labels . FromStrings ( "__name__" , "sum:histogram_metric" ) , s . Labels ( ) )
expHist := hists [ 0 ] . ToFloat ( )
for _ , h := range hists [ 1 : ] {
expHist = expHist . Add ( h . ToFloat ( ) )
}
it := s . Iterator ( nil )
require . Equal ( t , chunkenc . ValFloatHistogram , it . Next ( ) )
tsp , fh := it . AtFloatHistogram ( )
require . Equal ( t , ts . Add ( 10 * time . Second ) . UnixMilli ( ) , tsp )
require . Equal ( t , expHist , fh )
require . Equal ( t , chunkenc . ValNone , it . Next ( ) )
}