feat(monitor): support create nodata alert from web

1.支持从前端页面创建nodata报警策略,对于nodata的情况给出对应的resource
This commit is contained in:
zhaoxiangchun
2020-12-15 17:17:31 +08:00
parent 2b2ea4872f
commit 805ea5c0f5
8 changed files with 139 additions and 81 deletions
+6 -8
View File
@@ -165,14 +165,12 @@ type ResultLogEntry struct {
// EvalMatch represents the series violating the threshold.
type EvalMatch struct {
Condition string `json:"condition"`
Value *float64 `json:"value"`
ValueStr string `json:"value_str"`
Metric string `json:"metric"`
MeasurementDesc string `json:"measurement_desc"`
FieldDesc string `json:"field_desc"`
Tags map[string]string `json:"tags"`
Unit string `json:"unit"`
Condition string `json:"condition"`
Value *float64 `json:"value"`
ValueStr string `json:"value_str"`
Metric string `json:"metric"`
Tags map[string]string `json:"tags"`
Unit string `json:"unit"`
}
type AlertTestRunOutput struct {
+1
View File
@@ -36,6 +36,7 @@ type AlertRecordRule struct {
Metric string `json:"metric"`
Measurement string `json:"measurement"`
MeasurementDesc string `json:"measurement_desc"`
ResType string `json:"res_type"`
Field string `json:"field"`
FieldDesc string `json:"field_desc"`
// 比较运算符, 比如: >, <, >=, <=
+8
View File
@@ -20,6 +20,14 @@ var (
"oss": "oss_name",
"cloudaccount": "cloudaccount_name",
}
MEASUREMENT_TAG_ID = map[string]string{
"host": "host_id",
"guest": "vm_id",
"redis": "redis_id",
"rds": "rds_id",
"oss": "oss_id",
"cloudaccount": "cloudaccount_id",
}
AlertReduceFunc = map[string]string{
"avg": "average value",
"sum": "Summation",
+60 -17
View File
@@ -3,7 +3,6 @@ package conditions
import (
"context"
"fmt"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
@@ -50,8 +49,22 @@ func (c *NoDataQueryCondition) Eval(context *alerting.EvalContext) (*alerting.Co
normalHostIds := make(map[string]*monitor.EvalMatch, 0)
serLoop:
for _, series := range seriesList {
tagId := monitor.MEASUREMENT_TAG_ID[context.Rule.RuleDescription[0].ResType]
if len(tagId) == 0 {
tagId = "host_id"
}
for key, val := range series.Tags {
if strings.Contains(key, "host_id") {
if key == tagId {
if len(context.Rule.RuleDescription) == 0 {
return &alerting.ConditionResult{
Firing: false,
NoDataFound: true,
Operator: c.Operator,
EvalMatches: matches,
AlertOkEvalMatches: alertOkmatches,
}, nil
}
reducedValue, valStrArr := c.Reducer.Reduce(series)
match, err := c.NewEvalMatch(context, *series, nil, reducedValue, valStrArr)
if err != nil {
@@ -62,7 +75,7 @@ serLoop:
}
}
}
allHosts, err := c.getOnecloudHosts()
allHosts, err := c.getOnecloudResources(context)
if err != nil {
return nil, errors.Wrap(err, "NoDataQueryCondition getOnecloudHosts error")
}
@@ -73,10 +86,10 @@ serLoop:
return nil, errors.Wrap(err, "NewNoDataEvalMatch error")
}
if normalMatch, ok := normalHostIds[id]; !ok {
c.createEvalMatchTagFromHostJson(evalMatch, host)
c.createEvalMatchTagFromHostJson(context, evalMatch, host)
matches = append(matches, evalMatch)
} else {
c.createEvalMatchTagFromHostJson(normalMatch, host)
c.createEvalMatchTagFromHostJson(context, normalMatch, host)
alertOkmatches = append(alertOkmatches, normalMatch)
}
}
@@ -89,15 +102,37 @@ serLoop:
}, nil
}
func (c *NoDataQueryCondition) getOnecloudHosts() ([]jsonutils.JSONObject, error) {
func (c *NoDataQueryCondition) getOnecloudResources(evalContext *alerting.EvalContext) ([]jsonutils.JSONObject, error) {
var err error
allResources := make([]jsonutils.JSONObject, 0)
if len(evalContext.Rule.RuleDescription) == 0 {
return []jsonutils.JSONObject{}, nil
}
query := jsonutils.NewDict()
query.Set("brand", jsonutils.NewString(hostconsts.TELEGRAF_TAG_ONECLOUD_BRAND))
query.Set("host-type", jsonutils.NewString(hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR))
allHosts, err := ListAllResources(&mc_mds.Hosts, query)
query.Add(jsonutils.NewStringArray([]string{"running", "ready"}), "status")
query.Add(jsonutils.NewString("true"), "admin")
switch evalContext.Rule.RuleDescription[0].ResType {
case monitor.METRIC_RES_TYPE_HOST:
query.Set("host-type", jsonutils.NewString(hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR))
allResources, err = ListAllResources(&mc_mds.Hosts, query)
case monitor.METRIC_RES_TYPE_GUEST:
case monitor.METRIC_RES_TYPE_RDS:
allResources, err = ListAllResources(&mc_mds.DBInstance, query)
case monitor.METRIC_RES_TYPE_REDIS:
allResources, err = ListAllResources(&mc_mds.ElasticCache, query)
case monitor.METRIC_RES_TYPE_OSS:
allResources, err = ListAllResources(&mc_mds.Buckets, query)
default:
query := jsonutils.NewDict()
query.Set("brand", jsonutils.NewString(hostconsts.TELEGRAF_TAG_ONECLOUD_BRAND))
query.Set("host-type", jsonutils.NewString(hostconsts.TELEGRAF_TAG_KEY_HYPERVISOR))
allResources, err = ListAllResources(&mc_mds.Hosts, query)
}
if err != nil {
return nil, errors.Wrap(err, "NoDataQueryCondition Host list error")
}
return allHosts, nil
return allResources, nil
}
func ListAllResources(manager modulebase.Manager, params *jsonutils.JSONDict) ([]jsonutils.JSONObject, error) {
@@ -145,18 +180,17 @@ func (c *NoDataQueryCondition) NewNoDataEvalMatch(context *alerting.EvalContext,
}
evalMatch.Unit = alertDetails.FieldDescription.Unit
msg := fmt.Sprintf("%s.%s %s %s", alertDetails.Measurement, alertDetails.Field,
alertDetails.Comparator, c.RationalizeValueFromUnit(alertDetails.Threshold, evalMatch.Unit, ""))
alertDetails.Comparator, alerting.RationalizeValueFromUnit(alertDetails.Threshold, evalMatch.Unit, ""))
if len(context.Rule.Message) == 0 {
context.Rule.Message = msg
}
//evalMatch.Condition = c.GenerateFormatCond(meta, queryKeyInfo).String()
evalMatch.ValueStr = NO_DATA
evalMatch.MeasurementDesc = alertDetails.MeasurementDisplayName
evalMatch.FieldDesc = alertDetails.FieldDescription.DisplayName
return evalMatch, nil
}
func (c *NoDataQueryCondition) createEvalMatchTagFromHostJson(evalMatch *monitor.EvalMatch, host jsonutils.JSONObject) {
func (c *NoDataQueryCondition) createEvalMatchTagFromHostJson(evalContext *alerting.EvalContext, evalMatch *monitor.EvalMatch,
host jsonutils.JSONObject) {
evalMatch.Tags = make(map[string]string, 0)
ip, _ := host.GetString(HOST_TAG_IP)
@@ -164,11 +198,20 @@ func (c *NoDataQueryCondition) createEvalMatchTagFromHostJson(evalMatch *monitor
brand, _ := host.GetString(HOST_TAG_BRAND)
evalMatch.Tags["ip"] = ip
evalMatch.Tags[HOST_TAG_NAME] = name
evalMatch.Tags["host"] = name
evalMatch.Tags[HOST_TAG_BRAND] = brand
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_RES_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_RES_TYPE
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_HOST_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_HOST_TYPE_HOST
switch evalContext.Rule.RuleDescription[0].ResType {
case monitor.METRIC_RES_TYPE_HOST:
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_RES_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_RES_TYPE
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_HOST_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_HOST_TYPE_HOST
case monitor.METRIC_RES_TYPE_GUEST:
case monitor.METRIC_RES_TYPE_RDS:
case monitor.METRIC_RES_TYPE_REDIS:
case monitor.METRIC_RES_TYPE_OSS:
default:
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_RES_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_RES_TYPE
evalMatch.Tags[hostconsts.TELEGRAF_TAG_KEY_HOST_TYPE] = hostconsts.TELEGRAF_TAG_ONECLOUD_HOST_TYPE_HOST
}
}
func newNoDataQueryCondition(model *monitor.AlertCondition, index int) (*NoDataQueryCondition, error) {
+3 -51
View File
@@ -21,7 +21,6 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/monitor/alerting"
@@ -211,37 +210,22 @@ func (c *QueryCondition) NewEvalMatch(context *alerting.EvalContext, series tsdb
}
evalMatch.Unit = alertDetails.FieldDescription.Unit
msg := fmt.Sprintf("%s.%s %s %s", alertDetails.Measurement, alertDetails.Field,
alertDetails.Comparator, c.RationalizeValueFromUnit(alertDetails.Threshold, evalMatch.Unit, ""))
alertDetails.Comparator, alerting.RationalizeValueFromUnit(alertDetails.Threshold, evalMatch.Unit, ""))
if len(context.Rule.Message) == 0 {
context.Rule.Message = msg
}
//evalMatch.Condition = c.GenerateFormatCond(meta, queryKeyInfo).String()
evalMatch.Tags = c.filterTags(series.Tags, *alertDetails)
evalMatch.Value = value
evalMatch.ValueStr = c.RationalizeValueFromUnit(*value, alertDetails.FieldDescription.Unit,
evalMatch.ValueStr = alerting.RationalizeValueFromUnit(*value, alertDetails.FieldDescription.Unit,
alertDetails.FieldOpt)
if alertDetails.GetPointStr {
evalMatch.ValueStr = c.jointPointStr(series, evalMatch.ValueStr, valStrArr)
}
c.newRuleDescription(context, alertDetails)
//c.newRuleDescription(context, alertDetails)
return evalMatch, nil
}
func (c *QueryCondition) newRuleDescription(context *alerting.EvalContext, alertDetails *monitor.CommonAlertMetricDetails) {
ruleDes := alerting.RuleDescription{
AlertRecordRule: monitor.AlertRecordRule{
Metric: fmt.Sprintf("%s.%s", alertDetails.Measurement, alertDetails.Field),
Measurement: alertDetails.Measurement,
MeasurementDesc: alertDetails.MeasurementDisplayName,
Field: alertDetails.Field,
FieldDesc: alertDetails.FieldDescription.DisplayName,
Comparator: alertDetails.Comparator,
Threshold: c.RationalizeValueFromUnit(alertDetails.Threshold, alertDetails.FieldDescription.Unit, ""),
},
}
context.RuleDescription = &ruleDes
}
func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, valStrArr []string) string {
str := ""
for i := 0; i < len(valStrArr); i++ {
@@ -254,38 +238,6 @@ func (c *QueryCondition) jointPointStr(series tsdb.TimeSeries, value string, val
return str
}
var fileSize = []string{"bps", "Bps", "byte"}
func (c *QueryCondition) RationalizeValueFromUnit(value float64, unit string, opt string) string {
if utils.IsInStringArray(unit, fileSize) {
if unit == "byte" {
return (formatFileSize(value, unit, float64(1024)))
}
return formatFileSize(value, unit, float64(1000))
}
if unit == "%" && monitor.CommonAlertFieldOpt_Division == opt {
return fmt.Sprintf("%0.4f%s", value*100, unit)
}
return fmt.Sprintf("%0.4f%s", value, unit)
}
// 单位转换 保留四位小数
func formatFileSize(fileSize float64, unit string, unitsize float64) (size string) {
if fileSize < unitsize {
return fmt.Sprintf("%.4f%s", fileSize, unit)
} else if fileSize < (unitsize * unitsize) {
return fmt.Sprintf("%.4fK%s", float64(fileSize)/float64(unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fM%s", float64(fileSize)/float64(unitsize*unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fG%s", float64(fileSize)/float64(unitsize*unitsize*unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fT%s", float64(fileSize)/float64(unitsize*unitsize*unitsize*unitsize), unit)
} else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return fmt.Sprintf("%.4fE%s", float64(fileSize)/float64(unitsize*unitsize*unitsize*unitsize*unitsize), unit)
}
}
type queryResult struct {
series tsdb.TimeSeriesSlice
metas []tsdb.QueryResultMeta
+1 -1
View File
@@ -42,7 +42,7 @@ type EvalContext struct {
StartTime time.Time
EndTime time.Time
Rule *Rule
RuleDescription *RuleDescription
//RuleDescription *RuleDescription
NoDataFound bool
PrevAlertState monitor.AlertStateType
+2 -2
View File
@@ -219,8 +219,8 @@ func InitNotifier(config NotificationConfig) (Notifier, error) {
func newAlertRecordRule(evalCtx *EvalContext) monitor.AlertRecordRule {
alertRule := monitor.AlertRecordRule{}
if evalCtx.RuleDescription != nil {
alertRule = evalCtx.RuleDescription.AlertRecordRule
if len(evalCtx.Rule.RuleDescription) != 0 {
alertRule = evalCtx.Rule.RuleDescription[0].AlertRecordRule
}
if evalCtx.Rule.Frequency < 60 {
alertRule.Period = fmt.Sprintf("%ds", evalCtx.Rule.Frequency)
+58 -2
View File
@@ -16,12 +16,14 @@ package alerting
import (
"context"
"fmt"
"regexp"
"strconv"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/apis/monitor"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -56,7 +58,8 @@ type Rule struct {
Conditions []Condition
Notifications []string
// AlertRuleTags []*models.AlertRuleTag
Level string
Level string
RuleDescription []*RuleDescription
StateChanges int
}
@@ -114,6 +117,7 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
model.NoDataState = monitor.NoDataOption(ruleDef.NoDataState)
model.ExecutionErrorState = monitor.ExecutionErrorOption(ruleDef.ExecutionErrorState)
model.StateChanges = ruleDef.StateChanges
model.RuleDescription = make([]*RuleDescription, 0)
model.Frequency = ruleDef.Frequency
// frequency cannot be zero since that would not execute the alert rule.
@@ -138,8 +142,12 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
}
model.Notifications = nIds
// model.AlertRuleTags = ruleDef.GetTagsFromSettings()
alert, err := models.CommonAlertManager.GetAlert(ruleDef.Id)
if err != nil {
return nil, errors.Wrap(err, "GetCommonAlert error")
}
for index, condition := range settings.Conditions {
alertDetails := alert.GetCommonAlertMetricDetailsFromAlertCondition(index, &settings.Conditions[index])
condType := condition.Type
factory, exist := conditionFactories[condType]
if !exist {
@@ -149,6 +157,7 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
if err != nil {
return nil, errors.Wrapf(err, "construct query condition %s", jsonutils.Marshal(condition))
}
newRuleDescription(model, alertDetails)
model.Conditions = append(model.Conditions, queryCond)
}
@@ -158,6 +167,53 @@ func NewRuleFromDBAlert(ruleDef *models.SAlert) (*Rule, error) {
return model, nil
}
func newRuleDescription(rule *Rule, alertDetails *monitor.CommonAlertMetricDetails) {
ruleDes := RuleDescription{
AlertRecordRule: monitor.AlertRecordRule{
Metric: fmt.Sprintf("%s.%s", alertDetails.Measurement, alertDetails.Field),
Measurement: alertDetails.Measurement,
MeasurementDesc: alertDetails.MeasurementDisplayName,
Field: alertDetails.Field,
FieldDesc: alertDetails.FieldDescription.DisplayName,
Comparator: alertDetails.Comparator,
Threshold: RationalizeValueFromUnit(alertDetails.Threshold, alertDetails.FieldDescription.Unit, ""),
},
}
rule.RuleDescription = append(rule.RuleDescription, &ruleDes)
}
var fileSize = []string{"bps", "Bps", "byte"}
func RationalizeValueFromUnit(value float64, unit string, opt string) string {
if utils.IsInStringArray(unit, fileSize) {
if unit == "byte" {
return (FormatFileSize(value, unit, float64(1024)))
}
return FormatFileSize(value, unit, float64(1000))
}
if unit == "%" && monitor.CommonAlertFieldOpt_Division == opt {
return fmt.Sprintf("%0.4f%s", value*100, unit)
}
return fmt.Sprintf("%0.4f%s", value, unit)
}
// 单位转换 保留四位小数
func FormatFileSize(fileSize float64, unit string, unitsize float64) (size string) {
if fileSize < unitsize {
return fmt.Sprintf("%.4f%s", fileSize, unit)
} else if fileSize < (unitsize * unitsize) {
return fmt.Sprintf("%.4fK%s", float64(fileSize)/float64(unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fM%s", float64(fileSize)/float64(unitsize*unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fG%s", float64(fileSize)/float64(unitsize*unitsize*unitsize), unit)
} else if fileSize < (unitsize * unitsize * unitsize * unitsize * unitsize) {
return fmt.Sprintf("%.4fT%s", float64(fileSize)/float64(unitsize*unitsize*unitsize*unitsize), unit)
} else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return fmt.Sprintf("%.4fE%s", float64(fileSize)/float64(unitsize*unitsize*unitsize*unitsize*unitsize), unit)
}
}
type AlertRuleTester struct{}
func NewAlertRuleTester() models.AlertTestRunner {