2021-04-23 14:32:25 -05:00
package state
import (
2021-12-28 03:26:18 -06:00
"context"
2022-10-10 07:40:21 -05:00
"math"
2021-10-04 13:04:37 -05:00
"net/url"
2021-04-30 11:28:06 -05:00
"strings"
2021-04-23 14:32:25 -05:00
"sync"
2023-03-08 06:25:02 -06:00
"time"
2021-04-23 14:32:25 -05:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2023-03-08 06:25:02 -06:00
"github.com/hashicorp/go-multierror"
2021-04-23 14:32:25 -05:00
2021-04-28 10:42:19 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2021-04-23 14:32:25 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/eval"
2021-04-30 11:28:06 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
2021-04-23 14:32:25 -05:00
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
2023-02-16 10:16:36 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/state/template"
2021-04-23 14:32:25 -05:00
)
2022-10-06 14:30:12 -05:00
type ruleStates struct {
states map [ string ] * State
}
2021-04-23 14:32:25 -05:00
type cache struct {
2022-10-06 14:30:12 -05:00
states map [ int64 ] map [ string ] * ruleStates // orgID > alertRuleUID > stateID > state
mtxStates sync . RWMutex
2021-04-23 14:32:25 -05:00
}
2022-10-06 14:30:12 -05:00
func newCache ( ) * cache {
2021-04-23 14:32:25 -05:00
return & cache {
2022-10-06 14:30:12 -05:00
states : make ( map [ int64 ] map [ string ] * ruleStates ) ,
2021-04-23 14:32:25 -05:00
}
}
2022-10-06 14:30:12 -05:00
func ( c * cache ) getOrCreate ( ctx context . Context , log log . Logger , alertRule * ngModels . AlertRule , result eval . Result , extraLabels data . Labels , externalURL * url . URL ) * State {
2021-04-23 14:32:25 -05:00
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
2022-10-06 14:30:12 -05:00
var orgStates map [ string ] * ruleStates
var ok bool
if orgStates , ok = c . states [ alertRule . OrgID ] ; ! ok {
orgStates = make ( map [ string ] * ruleStates )
c . states [ alertRule . OrgID ] = orgStates
}
var states * ruleStates
if states , ok = orgStates [ alertRule . UID ] ; ! ok {
states = & ruleStates { states : make ( map [ string ] * State ) }
c . states [ alertRule . OrgID ] [ alertRule . UID ] = states
}
return states . getOrCreate ( ctx , log , alertRule , result , extraLabels , externalURL )
}
2021-04-23 14:32:25 -05:00
2022-10-06 14:30:12 -05:00
func ( rs * ruleStates ) getOrCreate ( ctx context . Context , log log . Logger , alertRule * ngModels . AlertRule , result eval . Result , extraLabels data . Labels , externalURL * url . URL ) * State {
2023-03-08 06:25:02 -06:00
// Merge both the extra labels and the labels from the evaluation into a common set
// of labels that can be expanded in custom labels and annotations.
templateData := template . NewData ( mergeLabels ( extraLabels , result . Instance ) , result )
// For now, do nothing with these errors as they are already logged in expand.
// In the future, we want to show these errors to the user somehow.
labels , _ := expand ( ctx , log , alertRule . Title , alertRule . Labels , templateData , externalURL , result . EvaluatedAt )
annotations , _ := expand ( ctx , log , alertRule . Title , alertRule . Annotations , templateData , externalURL , result . EvaluatedAt )
2021-06-03 12:24:36 -05:00
2022-10-10 07:40:21 -05:00
values := make ( map [ string ] float64 )
2023-03-06 11:08:00 -06:00
for refID , v := range result . Values {
2022-10-10 07:40:21 -05:00
if v . Value != nil {
2023-03-06 11:08:00 -06:00
values [ refID ] = * v . Value
2022-10-10 07:40:21 -05:00
} else {
2023-03-06 11:08:00 -06:00
values [ refID ] = math . NaN ( )
2022-10-10 07:40:21 -05:00
}
}
2023-03-08 06:25:02 -06:00
lbs := make ( data . Labels , len ( extraLabels ) + len ( labels ) + len ( result . Instance ) )
2022-07-14 14:59:59 -05:00
dupes := make ( data . Labels )
for key , val := range extraLabels {
lbs [ key ] = val
}
2023-03-08 06:25:02 -06:00
for key , val := range labels {
2022-10-07 14:06:53 -05:00
ruleVal , ok := lbs [ key ]
2022-07-14 14:59:59 -05:00
// if duplicate labels exist, reserved label will take precedence
if ok {
2022-10-07 14:06:53 -05:00
if ruleVal != val {
dupes [ key ] = val
}
2022-07-14 14:59:59 -05:00
} else {
lbs [ key ] = val
}
}
if len ( dupes ) > 0 {
2022-10-07 14:06:53 -05:00
log . Warn ( "Rule declares one or many reserved labels. Those rules labels will be ignored" , "labels" , dupes )
2022-07-14 14:59:59 -05:00
}
dupes = make ( data . Labels )
for key , val := range result . Instance {
_ , ok := lbs [ key ]
// if duplicate labels exist, reserved or alert rule label will take precedence
if ok {
dupes [ key ] = val
} else {
lbs [ key ] = val
}
}
if len ( dupes ) > 0 {
2022-10-21 16:16:51 -05:00
log . Warn ( "Evaluation result contains either reserved labels or labels declared in the rules. Those labels from the result will be ignored" , "labels" , dupes )
2022-07-14 14:59:59 -05:00
}
2021-04-23 14:32:25 -05:00
2021-04-28 10:42:19 -05:00
il := ngModels . InstanceLabels ( lbs )
id , err := il . StringKey ( )
if err != nil {
2022-10-21 16:16:51 -05:00
log . Error ( "Error getting cacheId for entry" , "error" , err )
2021-05-04 11:57:50 -05:00
}
2022-10-06 14:30:12 -05:00
if state , ok := rs . states [ id ] ; ok {
2022-05-18 04:21:18 -05:00
// Annotations can change over time, however we also want to maintain
// certain annotations across evaluations
for k , v := range state . Annotations {
if _ , ok := ngModels . InternalAnnotationNameSet [ k ] ; ok {
// If the annotation is not present then it should be copied from the
// previous state to the next state
if _ , ok := annotations [ k ] ; ! ok {
annotations [ k ] = v
}
}
}
2021-06-03 12:24:36 -05:00
state . Annotations = annotations
2022-10-10 07:40:21 -05:00
state . Values = values
2022-10-06 14:30:12 -05:00
rs . states [ id ] = state
2021-04-23 14:32:25 -05:00
return state
}
2023-03-31 11:05:15 -05:00
// For new states, we set StartsAt & EndsAt to EvaluatedAt as this is the
// expected value for a Normal state during state transition.
2021-04-23 14:32:25 -05:00
newState := & State {
AlertRuleUID : alertRule . UID ,
OrgID : alertRule . OrgID ,
2022-10-11 03:30:33 -05:00
CacheID : id ,
2021-04-23 14:32:25 -05:00
Labels : lbs ,
Annotations : annotations ,
EvaluationDuration : result . EvaluationDuration ,
2022-10-10 07:40:21 -05:00
Values : values ,
2023-03-31 11:05:15 -05:00
StartsAt : result . EvaluatedAt ,
EndsAt : result . EvaluatedAt ,
2021-04-23 14:32:25 -05:00
}
2022-10-06 14:30:12 -05:00
rs . states [ id ] = newState
2021-04-23 14:32:25 -05:00
return newState
}
2023-03-08 06:25:02 -06:00
// expand returns the expanded templates of all annotations or labels for the template data.
// If a template cannot be expanded due to an error in the template the original template is
// maintained and an error is added to the multierror. All errors in the multierror are
// template.ExpandError errors.
func expand ( ctx context . Context , log log . Logger , name string , original map [ string ] string , data template . Data , externalURL * url . URL , evaluatedAt time . Time ) ( map [ string ] string , error ) {
var (
errs * multierror . Error
expanded = make ( map [ string ] string , len ( original ) )
)
for k , v := range original {
result , err := template . Expand ( ctx , name , v , data , externalURL , evaluatedAt )
if err != nil {
log . Error ( "Error in expanding template" , "error" , err )
errs = multierror . Append ( errs , err )
// keep the original template on error
expanded [ k ] = v
} else {
expanded [ k ] = result
2021-06-03 12:24:36 -05:00
}
}
2023-03-08 06:25:02 -06:00
return expanded , errs . ErrorOrNil ( )
2021-06-03 12:24:36 -05:00
}
2022-11-07 10:03:53 -06:00
func ( rs * ruleStates ) deleteStates ( predicate func ( s * State ) bool ) [ ] * State {
deleted := make ( [ ] * State , 0 )
for id , state := range rs . states {
if predicate ( state ) {
delete ( rs . states , id )
deleted = append ( deleted , state )
}
}
return deleted
}
func ( c * cache ) deleteRuleStates ( ruleKey ngModels . AlertRuleKey , predicate func ( s * State ) bool ) [ ] * State {
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
ruleStates , ok := c . states [ ruleKey . OrgID ] [ ruleKey . UID ]
if ok {
return ruleStates . deleteStates ( predicate )
}
return nil
}
2022-10-06 14:30:12 -05:00
func ( c * cache ) setAllStates ( newStates map [ int64 ] map [ string ] * ruleStates ) {
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
c . states = newStates
}
2021-04-23 14:32:25 -05:00
func ( c * cache ) set ( entry * State ) {
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
2021-05-04 11:57:50 -05:00
if _ , ok := c . states [ entry . OrgID ] ; ! ok {
2022-10-06 14:30:12 -05:00
c . states [ entry . OrgID ] = make ( map [ string ] * ruleStates )
2021-05-04 11:57:50 -05:00
}
if _ , ok := c . states [ entry . OrgID ] [ entry . AlertRuleUID ] ; ! ok {
2022-10-06 14:30:12 -05:00
c . states [ entry . OrgID ] [ entry . AlertRuleUID ] = & ruleStates { states : make ( map [ string ] * State ) }
2021-05-04 11:57:50 -05:00
}
2022-10-11 03:30:33 -05:00
c . states [ entry . OrgID ] [ entry . AlertRuleUID ] . states [ entry . CacheID ] = entry
2021-04-23 14:32:25 -05:00
}
2022-10-06 14:30:12 -05:00
func ( c * cache ) get ( orgID int64 , alertRuleUID , stateId string ) * State {
2021-05-18 12:56:14 -05:00
c . mtxStates . RLock ( )
defer c . mtxStates . RUnlock ( )
2022-10-06 14:30:12 -05:00
ruleStates , ok := c . states [ orgID ] [ alertRuleUID ]
if ok {
var state * State
state , ok = ruleStates . states [ stateId ]
if ok {
return state
}
2021-04-23 14:32:25 -05:00
}
2022-10-06 14:30:12 -05:00
return nil
2021-04-23 14:32:25 -05:00
}
2023-01-13 17:29:29 -06:00
func ( c * cache ) getAll ( orgID int64 , skipNormalState bool ) [ ] * State {
2021-04-23 14:32:25 -05:00
var states [ ] * State
2021-05-18 12:56:14 -05:00
c . mtxStates . RLock ( )
defer c . mtxStates . RUnlock ( )
2021-05-04 11:57:50 -05:00
for _ , v1 := range c . states [ orgID ] {
2022-10-06 14:30:12 -05:00
for _ , v2 := range v1 . states {
2023-01-13 17:29:29 -06:00
if skipNormalState && IsNormalStateWithNoReason ( v2 ) {
continue
}
2021-05-04 11:57:50 -05:00
states = append ( states , v2 )
}
2021-04-23 14:32:25 -05:00
}
return states
}
2023-01-13 17:29:29 -06:00
func ( c * cache ) getStatesForRuleUID ( orgID int64 , alertRuleUID string , skipNormalState bool ) [ ] * State {
2021-05-18 12:56:14 -05:00
c . mtxStates . RLock ( )
defer c . mtxStates . RUnlock ( )
2022-10-06 14:30:12 -05:00
orgRules , ok := c . states [ orgID ]
if ! ok {
return nil
}
rs , ok := orgRules [ alertRuleUID ]
if ! ok {
return nil
}
2023-01-11 11:03:37 -06:00
result := make ( [ ] * State , 0 , len ( rs . states ) )
2022-10-06 14:30:12 -05:00
for _ , state := range rs . states {
2023-01-13 17:29:29 -06:00
if skipNormalState && IsNormalStateWithNoReason ( state ) {
continue
}
2022-10-06 14:30:12 -05:00
result = append ( result , state )
2021-04-23 14:32:25 -05:00
}
2022-10-06 14:30:12 -05:00
return result
2021-04-23 14:32:25 -05:00
}
2022-08-25 13:12:22 -05:00
// removeByRuleUID deletes all entries in the state cache that match the given UID. Returns removed states
func ( c * cache ) removeByRuleUID ( orgID int64 , uid string ) [ ] * State {
2021-05-03 13:01:33 -05:00
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
2022-10-06 14:30:12 -05:00
orgStates , ok := c . states [ orgID ]
if ! ok {
return nil
}
rs , ok := orgStates [ uid ]
if ! ok {
return nil
}
2021-05-04 11:57:50 -05:00
delete ( c . states [ orgID ] , uid )
2022-10-06 14:30:12 -05:00
if len ( rs . states ) == 0 {
2022-08-25 13:12:22 -05:00
return nil
}
2022-10-06 14:30:12 -05:00
states := make ( [ ] * State , 0 , len ( rs . states ) )
for _ , state := range rs . states {
2022-08-25 13:12:22 -05:00
states = append ( states , state )
}
return states
2021-05-03 13:01:33 -05:00
}
2022-10-06 14:30:12 -05:00
func ( c * cache ) recordMetrics ( metrics * metrics . State ) {
2021-05-18 12:56:14 -05:00
c . mtxStates . RLock ( )
defer c . mtxStates . RUnlock ( )
2021-04-30 11:28:06 -05:00
2021-05-03 10:57:24 -05:00
// Set default values to zero such that gauges are reset
// after all values from a single state disappear.
ct := map [ eval . State ] int {
eval . Normal : 0 ,
eval . Alerting : 0 ,
eval . Pending : 0 ,
eval . NoData : 0 ,
eval . Error : 0 ,
}
2021-04-30 11:28:06 -05:00
2023-02-09 10:05:19 -06:00
for _ , orgMap := range c . states {
2021-05-14 15:13:44 -05:00
for _ , rule := range orgMap {
2022-10-06 14:30:12 -05:00
for _ , state := range rule . states {
2021-05-04 11:57:50 -05:00
n := ct [ state . State ]
ct [ state . State ] = n + 1
}
2021-04-23 14:32:25 -05:00
}
2021-04-30 11:28:06 -05:00
}
for k , n := range ct {
2022-10-06 14:30:12 -05:00
metrics . AlertState . WithLabelValues ( strings . ToLower ( k . String ( ) ) ) . Set ( float64 ( n ) )
2021-04-23 14:32:25 -05:00
}
}
// if duplicate labels exist, keep the value from the first set
func mergeLabels ( a , b data . Labels ) data . Labels {
2022-07-14 14:59:59 -05:00
newLbs := make ( data . Labels , len ( a ) + len ( b ) )
2021-04-23 14:32:25 -05:00
for k , v := range a {
newLbs [ k ] = v
}
for k , v := range b {
if _ , ok := newLbs [ k ] ; ! ok {
newLbs [ k ] = v
}
}
return newLbs
}