2021-04-23 14:32:25 -05:00
package state
import (
2021-12-28 03:26:18 -06:00
"context"
2021-04-23 14:32:25 -05:00
"fmt"
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"
"github.com/grafana/grafana-plugin-sdk-go/data"
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"
)
type cache struct {
2021-10-04 13:04:37 -05:00
states map [ int64 ] map [ string ] map [ string ] * State // orgID > alertRuleUID > stateID > state
mtxStates sync . RWMutex
log log . Logger
metrics * metrics . State
externalURL * url . URL
2021-04-23 14:32:25 -05:00
}
2021-10-04 13:04:37 -05:00
func newCache ( logger log . Logger , metrics * metrics . State , externalURL * url . URL ) * cache {
2021-04-23 14:32:25 -05:00
return & cache {
2021-10-04 13:04:37 -05:00
states : make ( map [ int64 ] map [ string ] map [ string ] * State ) ,
log : logger ,
metrics : metrics ,
externalURL : externalURL ,
2021-04-23 14:32:25 -05:00
}
}
2022-07-14 14:59:59 -05:00
func ( c * cache ) getOrCreate ( ctx context . Context , alertRule * ngModels . AlertRule , result eval . Result , extraLabels data . Labels ) * State {
2021-04-23 14:32:25 -05:00
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
2022-07-14 14:59:59 -05:00
ruleLabels , annotations := c . expandRuleLabelsAndAnnotations ( ctx , alertRule , result , extraLabels )
2021-06-03 12:24:36 -05:00
2022-07-14 14:59:59 -05:00
lbs := make ( data . Labels , len ( extraLabels ) + len ( ruleLabels ) + len ( result . Instance ) )
dupes := make ( data . Labels )
for key , val := range extraLabels {
lbs [ key ] = val
}
for key , val := range ruleLabels {
_ , ok := lbs [ key ]
// if duplicate labels exist, reserved label will take precedence
if ok {
dupes [ key ] = val
} else {
lbs [ key ] = val
}
}
if len ( dupes ) > 0 {
c . log . Warn ( "rule declares one or many reserved labels. Those rules labels will be ignored" , "labels" , dupes )
}
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 {
c . log . Warn ( "evaluation result contains either reserved labels or labels declared in the rules. Those labels from the result will be ignored" , "labels" , dupes )
}
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 {
2021-06-03 12:24:36 -05:00
c . log . Error ( "error getting cacheId for entry" , "err" , err . Error ( ) )
2021-04-28 10:42:19 -05:00
}
2021-05-04 11:57:50 -05:00
if _ , ok := c . states [ alertRule . OrgID ] ; ! ok {
c . states [ alertRule . OrgID ] = make ( map [ string ] map [ string ] * State )
2021-05-06 07:35:52 -05:00
}
if _ , ok := c . states [ alertRule . OrgID ] [ alertRule . UID ] ; ! ok {
2021-05-04 11:57:50 -05:00
c . states [ alertRule . OrgID ] [ alertRule . UID ] = make ( map [ string ] * State )
}
if state , ok := c . states [ alertRule . OrgID ] [ alertRule . UID ] [ 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
c . states [ alertRule . OrgID ] [ alertRule . UID ] [ id ] = state
2021-04-23 14:32:25 -05:00
return state
}
// If the first result we get is alerting, set StartsAt to EvaluatedAt because we
// do not have data for determining StartsAt otherwise
newState := & State {
AlertRuleUID : alertRule . UID ,
OrgID : alertRule . OrgID ,
CacheId : id ,
Labels : lbs ,
Annotations : annotations ,
EvaluationDuration : result . EvaluationDuration ,
}
if result . State == eval . Alerting {
newState . StartsAt = result . EvaluatedAt
}
2021-05-04 11:57:50 -05:00
c . states [ alertRule . OrgID ] [ alertRule . UID ] [ id ] = newState
2021-04-23 14:32:25 -05:00
return newState
}
2022-07-14 14:59:59 -05:00
func ( c * cache ) expandRuleLabelsAndAnnotations ( ctx context . Context , alertRule * ngModels . AlertRule , alertInstance eval . Result , extraLabels data . Labels ) ( data . Labels , data . Labels ) {
// use labels from the result and extra labels to expand the labels and annotations declared by the rule
templateLabels := mergeLabels ( extraLabels , alertInstance . Instance )
2021-06-03 12:24:36 -05:00
expand := func ( original map [ string ] string ) map [ string ] string {
expanded := make ( map [ string ] string , len ( original ) )
for k , v := range original {
2022-07-14 14:59:59 -05:00
ev , err := expandTemplate ( ctx , alertRule . Title , v , templateLabels , alertInstance , c . externalURL )
2021-06-03 12:24:36 -05:00
expanded [ k ] = ev
if err != nil {
c . log . Error ( "error in expanding template" , "name" , k , "value" , v , "err" , err . Error ( ) )
// Store the original template on error.
expanded [ k ] = v
}
}
return expanded
}
return expand ( alertRule . Labels ) , expand ( alertRule . Annotations )
}
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 {
c . states [ entry . OrgID ] = make ( map [ string ] map [ string ] * State )
}
if _ , ok := c . states [ entry . OrgID ] [ entry . AlertRuleUID ] ; ! ok {
c . states [ entry . OrgID ] [ entry . AlertRuleUID ] = make ( map [ string ] * State )
}
c . states [ entry . OrgID ] [ entry . AlertRuleUID ] [ entry . CacheId ] = entry
2021-04-23 14:32:25 -05:00
}
2021-05-04 11:57:50 -05:00
func ( c * cache ) get ( orgID int64 , alertRuleUID , stateId string ) ( * State , error ) {
2021-05-18 12:56:14 -05:00
c . mtxStates . RLock ( )
defer c . mtxStates . RUnlock ( )
2021-05-04 11:57:50 -05:00
if state , ok := c . states [ orgID ] [ alertRuleUID ] [ stateId ] ; ok {
2021-04-23 14:32:25 -05:00
return state , nil
}
2021-05-04 11:57:50 -05:00
return nil , fmt . Errorf ( "no entry for %s:%s was found" , alertRuleUID , stateId )
2021-04-23 14:32:25 -05:00
}
2021-05-04 11:57:50 -05:00
func ( c * cache ) getAll ( orgID int64 ) [ ] * 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 ] {
for _ , v2 := range v1 {
states = append ( states , v2 )
}
2021-04-23 14:32:25 -05:00
}
return states
}
2021-05-04 11:57:50 -05:00
func ( c * cache ) getStatesForRuleUID ( orgID int64 , alertRuleUID string ) [ ] * State {
var ruleStates [ ] * 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 _ , state := range c . states [ orgID ] [ alertRuleUID ] {
ruleStates = append ( ruleStates , state )
2021-04-23 14:32:25 -05:00
}
2021-05-04 11:57:50 -05:00
return ruleStates
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-08-25 13:12:22 -05:00
statesMap := c . states [ orgID ] [ uid ]
2021-05-04 11:57:50 -05:00
delete ( c . states [ orgID ] , uid )
2022-08-25 13:12:22 -05:00
if statesMap == nil {
return nil
}
states := make ( [ ] * State , 0 , len ( statesMap ) )
for _ , state := range statesMap {
states = append ( states , state )
}
return states
2021-05-03 13:01:33 -05:00
}
2021-04-23 14:32:25 -05:00
func ( c * cache ) reset ( ) {
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
2021-05-04 11:57:50 -05:00
c . states = make ( map [ int64 ] map [ string ] map [ string ] * State )
2021-04-23 14:32:25 -05:00
}
2021-05-18 12:56:14 -05:00
func ( c * cache ) recordMetrics ( ) {
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
2021-05-14 15:13:44 -05:00
for org , orgMap := range c . states {
c . metrics . GroupRules . WithLabelValues ( fmt . Sprint ( org ) ) . Set ( float64 ( len ( orgMap ) ) )
for _ , rule := range orgMap {
2021-05-04 11:57:50 -05:00
for _ , state := range rule {
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 {
c . 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
}
2021-07-26 11:12:04 -05:00
func ( c * cache ) deleteEntry ( orgID int64 , alertRuleUID , cacheID string ) {
c . mtxStates . Lock ( )
defer c . mtxStates . Unlock ( )
delete ( c . states [ orgID ] [ alertRuleUID ] , cacheID )
}