2024-09-09 10:28:54 +05:30
package rules
import (
2024-11-08 21:10:09 +05:30
"context"
2024-09-09 10:28:54 +05:30
"fmt"
"time"
2025-03-20 21:01:41 +05:30
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
2024-11-08 21:10:09 +05:30
"github.com/google/uuid"
"go.uber.org/zap"
2024-09-09 10:28:54 +05:30
)
func PrepareTaskFunc ( opts baserules . PrepareTaskOptions ) ( baserules . Task , error ) {
rules := make ( [ ] baserules . Rule , 0 )
var task baserules . Task
ruleId := baserules . RuleIdFromTaskName ( opts . TaskName )
if opts . Rule . RuleType == baserules . RuleTypeThreshold {
// create a threshold rule
tr , err := baserules . NewThresholdRule (
ruleId ,
opts . Rule ,
opts . FF ,
opts . Reader ,
2024-09-12 10:58:07 +05:30
opts . UseLogsNewSchema ,
2024-11-22 12:00:29 +05:30
opts . UseTraceNewSchema ,
2024-09-11 09:56:59 +05:30
baserules . WithEvalDelay ( opts . ManagerOpts . EvalDelay ) ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-09-09 10:28:54 +05:30
)
if err != nil {
return task , err
}
rules = append ( rules , tr )
// create ch rule task for evalution
task = newTask ( baserules . TaskTypeCh , opts . TaskName , time . Duration ( opts . Rule . Frequency ) , rules , opts . ManagerOpts , opts . NotifyFunc , opts . RuleDB )
} else if opts . Rule . RuleType == baserules . RuleTypeProm {
// create promql rule
pr , err := baserules . NewPromRule (
ruleId ,
opts . Rule ,
opts . Logger ,
opts . Reader ,
2024-09-11 09:56:59 +05:30
opts . ManagerOpts . PqlEngine ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-09-09 10:28:54 +05:30
)
if err != nil {
return task , err
}
rules = append ( rules , pr )
// create promql rule task for evalution
task = newTask ( baserules . TaskTypeProm , opts . TaskName , time . Duration ( opts . Rule . Frequency ) , rules , opts . ManagerOpts , opts . NotifyFunc , opts . RuleDB )
2024-09-24 10:22:52 +05:30
} else if opts . Rule . RuleType == baserules . RuleTypeAnomaly {
// create anomaly rule
ar , err := NewAnomalyRule (
ruleId ,
opts . Rule ,
opts . FF ,
opts . Reader ,
opts . Cache ,
baserules . WithEvalDelay ( opts . ManagerOpts . EvalDelay ) ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-09-24 10:22:52 +05:30
)
if err != nil {
return task , err
}
rules = append ( rules , ar )
// create anomaly rule task for evalution
task = newTask ( baserules . TaskTypeCh , opts . TaskName , time . Duration ( opts . Rule . Frequency ) , rules , opts . ManagerOpts , opts . NotifyFunc , opts . RuleDB )
2024-09-09 10:28:54 +05:30
} else {
2024-10-03 16:56:58 +05:30
return nil , fmt . Errorf ( "unsupported rule type %s. Supported types: %s, %s" , opts . Rule . RuleType , baserules . RuleTypeProm , baserules . RuleTypeThreshold )
2024-09-09 10:28:54 +05:30
}
return task , nil
}
2024-11-08 21:10:09 +05:30
// TestNotification prepares a dummy rule for given rule parameters and
// sends a test notification. returns alert count and error (if any)
func TestNotification ( opts baserules . PrepareTestRuleOptions ) ( int , * basemodel . ApiError ) {
ctx := context . Background ( )
if opts . Rule == nil {
return 0 , basemodel . BadRequest ( fmt . Errorf ( "rule is required" ) )
}
parsedRule := opts . Rule
var alertname = parsedRule . AlertName
if alertname == "" {
// alertname is not mandatory for testing, so picking
// a random string here
alertname = uuid . New ( ) . String ( )
}
// append name to indicate this is test alert
parsedRule . AlertName = fmt . Sprintf ( "%s%s" , alertname , baserules . TestAlertPostFix )
var rule baserules . Rule
var err error
if parsedRule . RuleType == baserules . RuleTypeThreshold {
// add special labels for test alerts
parsedRule . Annotations [ labels . AlertSummaryLabel ] = fmt . Sprintf ( "The rule threshold is set to %.4f, and the observed metric value is {{$value}}." , * parsedRule . RuleCondition . Target )
parsedRule . Labels [ labels . RuleSourceLabel ] = ""
parsedRule . Labels [ labels . AlertRuleIdLabel ] = ""
// create a threshold rule
rule , err = baserules . NewThresholdRule (
alertname ,
parsedRule ,
opts . FF ,
opts . Reader ,
opts . UseLogsNewSchema ,
2024-11-22 12:00:29 +05:30
opts . UseTraceNewSchema ,
2024-11-08 21:10:09 +05:30
baserules . WithSendAlways ( ) ,
baserules . WithSendUnmatched ( ) ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-11-08 21:10:09 +05:30
)
if err != nil {
zap . L ( ) . Error ( "failed to prepare a new threshold rule for test" , zap . String ( "name" , rule . Name ( ) ) , zap . Error ( err ) )
return 0 , basemodel . BadRequest ( err )
}
} else if parsedRule . RuleType == baserules . RuleTypeProm {
// create promql rule
rule , err = baserules . NewPromRule (
alertname ,
parsedRule ,
opts . Logger ,
opts . Reader ,
opts . ManagerOpts . PqlEngine ,
baserules . WithSendAlways ( ) ,
baserules . WithSendUnmatched ( ) ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-11-08 21:10:09 +05:30
)
if err != nil {
zap . L ( ) . Error ( "failed to prepare a new promql rule for test" , zap . String ( "name" , rule . Name ( ) ) , zap . Error ( err ) )
return 0 , basemodel . BadRequest ( err )
}
} else if parsedRule . RuleType == baserules . RuleTypeAnomaly {
// create anomaly rule
rule , err = NewAnomalyRule (
alertname ,
parsedRule ,
opts . FF ,
opts . Reader ,
opts . Cache ,
baserules . WithSendAlways ( ) ,
baserules . WithSendUnmatched ( ) ,
2025-03-10 01:30:42 +05:30
baserules . WithSQLStore ( opts . SQLStore ) ,
2024-11-08 21:10:09 +05:30
)
if err != nil {
zap . L ( ) . Error ( "failed to prepare a new anomaly rule for test" , zap . String ( "name" , rule . Name ( ) ) , zap . Error ( err ) )
return 0 , basemodel . BadRequest ( err )
}
} else {
return 0 , basemodel . BadRequest ( fmt . Errorf ( "failed to derive ruletype with given information" ) )
}
// set timestamp to current utc time
ts := time . Now ( ) . UTC ( )
count , err := rule . Eval ( ctx , ts )
if err != nil {
zap . L ( ) . Error ( "evaluating rule failed" , zap . String ( "rule" , rule . Name ( ) ) , zap . Error ( err ) )
return 0 , basemodel . InternalError ( fmt . Errorf ( "rule evaluation failed" ) )
}
alertsFound , ok := count . ( int )
if ! ok {
return 0 , basemodel . InternalError ( fmt . Errorf ( "something went wrong" ) )
}
rule . SendAlerts ( ctx , ts , 0 , time . Duration ( 1 * time . Minute ) , opts . NotifyFunc )
return alertsFound , nil
}
2024-09-09 10:28:54 +05:30
// newTask returns an appropriate group for
// rule type
func newTask ( taskType baserules . TaskType , name string , frequency time . Duration , rules [ ] baserules . Rule , opts * baserules . ManagerOptions , notify baserules . NotifyFunc , ruleDB baserules . RuleDB ) baserules . Task {
if taskType == baserules . TaskTypeCh {
return baserules . NewRuleTask ( name , "" , frequency , rules , opts , notify , ruleDB )
}
return baserules . NewPromRuleTask ( name , "" , frequency , rules , opts , notify , ruleDB )
}