2022-05-11 09:04:50 -05:00
package schedule
import (
"context"
2023-04-28 09:42:16 -05:00
"encoding/binary"
2022-08-25 13:12:22 -05:00
"errors"
2023-04-28 09:42:16 -05:00
"fmt"
"hash/fnv"
"math"
"sort"
2022-05-11 09:04:50 -05:00
"sync"
"time"
2023-04-28 09:42:16 -05:00
"unsafe"
2022-05-11 09:04:50 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
2022-08-25 13:12:22 -05:00
var errRuleDeleted = errors . New ( "rule deleted" )
2024-03-06 13:44:53 -06:00
type ruleFactory interface {
2024-03-11 15:57:38 -05:00
new ( context . Context ) Rule
2024-03-06 13:44:53 -06:00
}
2024-03-11 15:57:38 -05:00
type ruleRegistry struct {
mu sync . Mutex
rules map [ models . AlertRuleKey ] Rule
2022-05-11 09:04:50 -05:00
}
2024-03-11 15:57:38 -05:00
func newRuleRegistry ( ) ruleRegistry {
return ruleRegistry { rules : make ( map [ models . AlertRuleKey ] Rule ) }
}
// getOrCreate gets rule routine from registry by the key. If it does not exist, it creates a new one.
// Returns a pointer to the rule routine and a flag that indicates whether it is a new struct or not.
func ( r * ruleRegistry ) getOrCreate ( context context . Context , key models . AlertRuleKey , factory ruleFactory ) ( Rule , bool ) {
2022-05-11 09:04:50 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2024-03-11 15:57:38 -05:00
rule , ok := r . rules [ key ]
2022-05-11 09:04:50 -05:00
if ! ok {
2024-03-11 15:57:38 -05:00
rule = factory . new ( context )
r . rules [ key ] = rule
2022-05-11 09:04:50 -05:00
}
2024-03-11 15:57:38 -05:00
return rule , ! ok
2022-05-11 09:04:50 -05:00
}
2024-03-11 15:57:38 -05:00
func ( r * ruleRegistry ) exists ( key models . AlertRuleKey ) bool {
2022-05-11 09:04:50 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2024-03-11 15:57:38 -05:00
_ , ok := r . rules [ key ]
2022-05-11 09:04:50 -05:00
return ok
}
2024-03-11 15:57:38 -05:00
// del removes pair that has specific key from the registry.
2022-05-11 09:04:50 -05:00
// Returns 2-tuple where the first element is value of the removed pair
// and the second element indicates whether element with the specified key existed.
2024-03-11 15:57:38 -05:00
func ( r * ruleRegistry ) del ( key models . AlertRuleKey ) ( Rule , bool ) {
2022-05-11 09:04:50 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2024-03-11 15:57:38 -05:00
rule , ok := r . rules [ key ]
2022-05-11 09:04:50 -05:00
if ok {
2024-03-11 15:57:38 -05:00
delete ( r . rules , key )
2022-05-11 09:04:50 -05:00
}
2024-03-11 15:57:38 -05:00
return rule , ok
2022-05-11 09:04:50 -05:00
}
2024-03-11 15:57:38 -05:00
func ( r * ruleRegistry ) keyMap ( ) map [ models . AlertRuleKey ] struct { } {
2022-05-11 09:04:50 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2024-03-11 15:57:38 -05:00
definitionsIDs := make ( map [ models . AlertRuleKey ] struct { } , len ( r . rules ) )
for k := range r . rules {
2022-05-11 09:04:50 -05:00
definitionsIDs [ k ] = struct { } { }
}
return definitionsIDs
}
2024-03-11 15:57:38 -05:00
type RuleVersionAndPauseStatus struct {
2023-04-28 09:42:16 -05:00
Fingerprint fingerprint
IsPaused bool
2023-01-26 11:29:10 -06:00
}
2022-07-15 11:32:52 -05:00
2024-03-11 15:57:38 -05:00
type Evaluation struct {
2022-05-11 09:04:50 -05:00
scheduledAt time . Time
2022-07-26 08:40:06 -05:00
rule * models . AlertRule
2022-08-31 10:08:19 -05:00
folderTitle string
2022-05-11 09:04:50 -05:00
}
2022-06-07 10:20:06 -05:00
2022-07-26 08:40:06 -05:00
type alertRulesRegistry struct {
2022-08-31 10:08:19 -05:00
rules map [ models . AlertRuleKey ] * models . AlertRule
2024-01-30 16:14:11 -06:00
folderTitles map [ models . FolderKey ] string
2022-08-31 10:08:19 -05:00
mu sync . Mutex
2022-06-07 10:20:06 -05:00
}
// all returns all rules in the registry.
2024-01-30 16:14:11 -06:00
func ( r * alertRulesRegistry ) all ( ) ( [ ] * models . AlertRule , map [ models . FolderKey ] string ) {
2022-06-07 10:20:06 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2022-07-26 08:40:06 -05:00
result := make ( [ ] * models . AlertRule , 0 , len ( r . rules ) )
2022-06-07 10:20:06 -05:00
for _ , rule := range r . rules {
result = append ( result , rule )
}
2022-08-31 10:08:19 -05:00
return result , r . folderTitles
2022-06-07 10:20:06 -05:00
}
2022-07-26 08:40:06 -05:00
func ( r * alertRulesRegistry ) get ( k models . AlertRuleKey ) * models . AlertRule {
2022-06-07 10:20:06 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
return r . rules [ k ]
}
2023-03-14 17:02:51 -05:00
// set replaces all rules in the registry. Returns difference between previous and the new current version of the registry
2024-01-30 16:14:11 -06:00
func ( r * alertRulesRegistry ) set ( rules [ ] * models . AlertRule , folders map [ models . FolderKey ] string ) diff {
2022-06-07 10:20:06 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
2023-03-14 17:02:51 -05:00
rulesMap := make ( map [ models . AlertRuleKey ] * models . AlertRule )
2022-06-07 10:20:06 -05:00
for _ , rule := range rules {
2023-03-14 17:02:51 -05:00
rulesMap [ rule . GetKey ( ) ] = rule
2022-06-07 10:20:06 -05:00
}
2023-03-14 17:02:51 -05:00
d := r . getDiff ( rulesMap )
r . rules = rulesMap
2022-08-31 10:08:19 -05:00
// return the map as is without copying because it is not mutated
r . folderTitles = folders
2023-03-14 17:02:51 -05:00
return d
2022-06-07 10:20:06 -05:00
}
// update inserts or replaces a rule in the registry.
2022-07-26 08:40:06 -05:00
func ( r * alertRulesRegistry ) update ( rule * models . AlertRule ) {
2022-06-07 10:20:06 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
r . rules [ rule . GetKey ( ) ] = rule
}
2022-07-26 08:40:06 -05:00
// del removes pair that has specific key from alertRulesRegistry.
2022-06-08 12:37:33 -05:00
// Returns 2-tuple where the first element is value of the removed pair
// and the second element indicates whether element with the specified key existed.
2022-07-26 08:40:06 -05:00
func ( r * alertRulesRegistry ) del ( k models . AlertRuleKey ) ( * models . AlertRule , bool ) {
2022-06-07 10:20:06 -05:00
r . mu . Lock ( )
defer r . mu . Unlock ( )
rule , ok := r . rules [ k ]
if ok {
delete ( r . rules , k )
}
return rule , ok
}
2022-08-31 10:08:19 -05:00
func ( r * alertRulesRegistry ) isEmpty ( ) bool {
r . mu . Lock ( )
defer r . mu . Unlock ( )
return len ( r . rules ) == 0
}
func ( r * alertRulesRegistry ) needsUpdate ( keys [ ] models . AlertRuleKeyWithVersion ) bool {
if len ( r . rules ) != len ( keys ) {
return true
}
for _ , key := range keys {
rule , ok := r . rules [ key . AlertRuleKey ]
if ! ok || rule . Version != key . Version {
return true
}
}
return false
}
2023-03-14 17:02:51 -05:00
type diff struct {
updated map [ models . AlertRuleKey ] struct { }
}
func ( d diff ) IsEmpty ( ) bool {
return len ( d . updated ) == 0
}
// getDiff calculates difference between the list of rules fetched previously and provided keys. Returns diff where
// updated - a list of keys that exist in the registry but with different version,
func ( r * alertRulesRegistry ) getDiff ( rules map [ models . AlertRuleKey ] * models . AlertRule ) diff {
result := diff {
updated : map [ models . AlertRuleKey ] struct { } { } ,
}
for key , newRule := range rules {
oldRule , ok := r . rules [ key ]
if ! ok || newRule . Version == oldRule . Version {
// a new rule or not updated
continue
}
result . updated [ key ] = struct { } { }
}
return result
}
2023-04-28 09:42:16 -05:00
type fingerprint uint64
func ( f fingerprint ) String ( ) string {
return fmt . Sprintf ( "%016x" , uint64 ( f ) )
}
// fingerprintSeparator used during calculation of fingerprint to separate different fields. Contains a byte sequence that cannot happen in UTF-8 strings.
var fingerprintSeparator = [ ] byte { 255 }
type ruleWithFolder struct {
rule * models . AlertRule
folderTitle string
}
// fingerprint calculates a fingerprint that includes all fields except rule's Version and Update timestamp.
func ( r ruleWithFolder ) Fingerprint ( ) fingerprint {
rule := r . rule
sum := fnv . New64 ( )
writeBytes := func ( b [ ] byte ) {
_ , _ = sum . Write ( b )
_ , _ = sum . Write ( fingerprintSeparator )
}
writeString := func ( s string ) {
if len ( s ) == 0 {
writeBytes ( nil )
return
}
2024-03-25 09:28:24 -05:00
// #nosec G103
2023-04-28 09:42:16 -05:00
// avoid allocation when converting string to byte slice
2023-06-30 13:58:23 -05:00
writeBytes ( unsafe . Slice ( unsafe . StringData ( s ) , len ( s ) ) )
2023-04-28 09:42:16 -05:00
}
// this temp slice is used to convert ints to bytes.
tmp := make ( [ ] byte , 8 )
writeInt := func ( u int64 ) {
binary . LittleEndian . PutUint64 ( tmp , uint64 ( u ) )
writeBytes ( tmp )
}
// allocate a slice that will be used for sorting keys, so we allocate it only once
var keys [ ] string
maxLen := int ( math . Max ( math . Max ( float64 ( len ( rule . Annotations ) ) , float64 ( len ( rule . Labels ) ) ) , float64 ( len ( rule . Data ) ) ) )
if maxLen > 0 {
keys = make ( [ ] string , maxLen )
}
writeLabels := func ( lbls map [ string ] string ) {
// maps do not guarantee predictable sequence of keys.
// Therefore, to make hash stable, we need to sort keys
if len ( lbls ) == 0 {
return
}
idx := 0
for labelName := range lbls {
keys [ idx ] = labelName
idx ++
}
sub := keys [ : idx ]
sort . Strings ( sub )
for _ , name := range sub {
writeString ( name )
writeString ( lbls [ name ] )
}
}
writeQuery := func ( ) {
// The order of queries is not important as they represent an expression tree.
// Therefore, the order of elements should not change the hash. Sort by RefID because it is the unique key.
for i , q := range rule . Data {
keys [ i ] = q . RefID
}
sub := keys [ : len ( rule . Data ) ]
sort . Strings ( sub )
for _ , id := range sub {
for _ , q := range rule . Data {
if q . RefID == id {
writeString ( q . RefID )
writeString ( q . DatasourceUID )
writeString ( q . QueryType )
writeInt ( int64 ( q . RelativeTimeRange . From ) )
writeInt ( int64 ( q . RelativeTimeRange . To ) )
writeBytes ( q . Model )
break
}
}
}
}
// fields that determine the rule state
writeString ( rule . UID )
writeString ( rule . Title )
writeString ( rule . NamespaceUID )
writeString ( r . folderTitle )
writeLabels ( rule . Labels )
writeString ( rule . Condition )
writeQuery ( )
if rule . IsPaused {
writeInt ( 1 )
} else {
writeInt ( 0 )
}
2024-02-15 08:45:10 -06:00
for _ , setting := range rule . NotificationSettings {
binary . LittleEndian . PutUint64 ( tmp , uint64 ( setting . Fingerprint ( ) ) )
writeBytes ( tmp )
}
2023-04-28 09:42:16 -05:00
// fields that do not affect the state.
// TODO consider removing fields below from the fingerprint
writeInt ( rule . ID )
writeInt ( rule . OrgID )
writeInt ( int64 ( rule . For ) )
if rule . DashboardUID != nil {
writeString ( * rule . DashboardUID )
}
if rule . PanelID != nil {
writeInt ( * rule . PanelID )
}
writeString ( rule . RuleGroup )
writeInt ( int64 ( rule . RuleGroupIndex ) )
writeString ( string ( rule . NoDataState ) )
writeString ( string ( rule . ExecErrState ) )
return fingerprint ( sum . Sum64 ( ) )
}