未验证 提交 0f72e37c 编写于 作者: sinat_25235033's avatar sinat_25235033 提交者: GitHub

support alert threshold rule config system value row count (#1180)

上级 ee76ac11
......@@ -41,6 +41,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.persistence.criteria.Predicate;
import java.util.*;
......@@ -60,6 +61,8 @@ import static org.dromara.hertzbeat.common.constants.CommonConstants.ALERT_STATU
@Slf4j
public class CalculateAlarm {
private static final String SYSTEM_VALUE_ROW_COUNT = "system_value_row_count";
/**
* The alarm in the process is triggered
* 触发中告警信息
......@@ -134,89 +137,60 @@ public class CalculateAlarm {
}
List<CollectRep.Field> fields = metricsData.getFieldsList();
Map<String, Object> fieldValueMap = new HashMap<>(16);
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
if (!valueRow.getColumnsList().isEmpty()) {
fieldValueMap.clear();
String instance = valueRow.getInstance();
if (!"".equals(instance)) {
fieldValueMap.put("instance", instance);
int valueRowCount = metricsData.getValuesCount();
for (Map.Entry<String, List<AlertDefine>> entry : defineMap.entrySet()) {
List<AlertDefine> defines = entry.getValue();
for (AlertDefine define : defines) {
String expr = define.getExpr();
if (!StringUtils.hasText(expr)) {
continue;
}
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
String valueStr = valueRow.getColumns(index);
CollectRep.Field field = fields.get(index);
if (field.getType() == CommonConstants.TYPE_NUMBER) {
Double doubleValue = CommonUtil.parseStrDouble(valueStr);
if (doubleValue != null) {
fieldValueMap.put(field.getName(), doubleValue);
}
} else {
if (!"".equals(valueStr)) {
fieldValueMap.put(field.getName(), valueStr);
if (expr.contains(SYSTEM_VALUE_ROW_COUNT)) {
fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, valueRowCount);
try {
boolean match = execAlertExpression(fieldValueMap, expr);
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
// 阈值规则匹配,判断已触发阈值次数,触发告警
afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, fieldValueMap, define);
// 若此阈值已被触发,则其它数据行的触发忽略
continue;
}
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
}
for (Map.Entry<String, List<AlertDefine>> entry : defineMap.entrySet()) {
List<AlertDefine> defines = entry.getValue();
for (AlertDefine define : defines) {
String expr = define.getExpr();
try {
Boolean match = false;
try {
Expression expression = AviatorEvaluator.compile(expr, true);
match = (Boolean) expression.execute(fieldValueMap);
} catch (CompileExpressionErrorException |
ExpressionSyntaxErrorException compileException) {
log.error("Alert Define Rule: {} Compile Error: {}.", expr, compileException.getMessage());
} catch (ExpressionRuntimeException expressionRuntimeException) {
log.error("Alert Define Rule: {} Run Error: {}.", expr, expressionRuntimeException.getMessage());
} catch (Exception e) {
log.error("Alert Define Rule: {} Run Error: {}.", e, e.getMessage());
}
if (match != null && match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
// 阈值规则匹配,判断已触发阈值次数,触发告警
String monitorAlertKey = String.valueOf(monitorId) + define.getId();
Alert triggeredAlert = triggeredAlertMap.get(monitorAlertKey);
if (triggeredAlert != null) {
int times = triggeredAlert.getTriggerTimes() + 1;
triggeredAlert.setTriggerTimes(times);
triggeredAlert.setFirstAlarmTime(currentTimeMilli);
triggeredAlert.setLastAlarmTime(currentTimeMilli);
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (times >= defineTimes) {
triggeredAlertMap.remove(monitorAlertKey);
alarmCommonReduce.reduceAndSendAlarm(triggeredAlert.clone());
for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
if (!valueRow.getColumnsList().isEmpty()) {
fieldValueMap.clear();
fieldValueMap.put(SYSTEM_VALUE_ROW_COUNT, valueRowCount);
String instance = valueRow.getInstance();
if (!"".equals(instance)) {
fieldValueMap.put("instance", instance);
}
for (int index = 0; index < valueRow.getColumnsList().size(); index++) {
String valueStr = valueRow.getColumns(index);
if (!CommonConstants.NULL_VALUE.equals(valueStr)) {
CollectRep.Field field = fields.get(index);
if (field.getType() == CommonConstants.TYPE_NUMBER) {
Double doubleValue = CommonUtil.parseStrDouble(valueStr);
if (doubleValue != null) {
fieldValueMap.put(field.getName(), doubleValue);
}
} else {
fieldValueMap.put("app", app);
fieldValueMap.put("metrics", metrics);
fieldValueMap.put("metric", define.getField());
Map<String, String> tags = new HashMap<>(6);
tags.put(CommonConstants.TAG_MONITOR_ID, String.valueOf(monitorId));
tags.put(CommonConstants.TAG_MONITOR_APP, app);
Alert alert = Alert.builder()
.tags(tags)
.alertDefineId(define.getId())
.priority(define.getPriority())
.status(ALERT_STATUS_CODE_PENDING)
.target(app + "." + metrics + "." + define.getField())
.triggerTimes(1)
.firstAlarmTime(currentTimeMilli)
.lastAlarmTime(currentTimeMilli)
// Keyword matching and substitution in the template
// 模板中关键字匹配替换
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.build();
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (1 >= defineTimes) {
alarmCommonReduce.reduceAndSendAlarm(alert);
} else {
triggeredAlertMap.put(monitorAlertKey, alert);
if (!"".equals(valueStr)) {
fieldValueMap.put(field.getName(), valueStr);
}
}
// Threshold rules below this priority are ignored
// 此优先级以下的阈值规则则忽略
}
}
}
try {
boolean match = execAlertExpression(fieldValueMap, expr);
if (match) {
// If the threshold rule matches, the number of times the threshold has been triggered is determined and an alarm is triggered
// 阈值规则匹配,判断已触发阈值次数,触发告警
afterThresholdRuleMatch(currentTimeMilli, monitorId, app, metrics, fieldValueMap, define);
// 若此阈值已被触发,则其它数据行的触发忽略
break;
}
} catch (Exception e) {
......@@ -224,11 +198,68 @@ public class CalculateAlarm {
}
}
}
}
}
}
private void afterThresholdRuleMatch(long currentTimeMilli, long monitorId, String app, String metrics, Map<String, Object> fieldValueMap, AlertDefine define) {
String monitorAlertKey = String.valueOf(monitorId) + define.getId();
Alert triggeredAlert = triggeredAlertMap.get(monitorAlertKey);
if (triggeredAlert != null) {
int times = triggeredAlert.getTriggerTimes() + 1;
triggeredAlert.setTriggerTimes(times);
triggeredAlert.setFirstAlarmTime(currentTimeMilli);
triggeredAlert.setLastAlarmTime(currentTimeMilli);
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (times >= defineTimes) {
triggeredAlertMap.remove(monitorAlertKey);
alarmCommonReduce.reduceAndSendAlarm(triggeredAlert.clone());
}
} else {
fieldValueMap.put("app", app);
fieldValueMap.put("metrics", metrics);
fieldValueMap.put("metric", define.getField());
Map<String, String> tags = new HashMap<>(6);
tags.put(CommonConstants.TAG_MONITOR_ID, String.valueOf(monitorId));
tags.put(CommonConstants.TAG_MONITOR_APP, app);
Alert alert = Alert.builder()
.tags(tags)
.alertDefineId(define.getId())
.priority(define.getPriority())
.status(ALERT_STATUS_CODE_PENDING)
.target(app + "." + metrics + "." + define.getField())
.triggerTimes(1)
.firstAlarmTime(currentTimeMilli)
.lastAlarmTime(currentTimeMilli)
// Keyword matching and substitution in the template
// 模板中关键字匹配替换
.content(AlertTemplateUtil.render(define.getTemplate(), fieldValueMap))
.build();
int defineTimes = define.getTimes() == null ? 1 : define.getTimes();
if (1 >= defineTimes) {
alarmCommonReduce.reduceAndSendAlarm(alert);
} else {
triggeredAlertMap.put(monitorAlertKey, alert);
}
}
}
private boolean execAlertExpression(Map<String, Object> fieldValueMap, String expr) {
Boolean match = false;
try {
Expression expression = AviatorEvaluator.compile(expr, true);
match = (Boolean) expression.execute(fieldValueMap);
} catch (CompileExpressionErrorException |
ExpressionSyntaxErrorException compileException) {
log.error("Alert Define Rule: {} Compile Error: {}.", expr, compileException.getMessage());
} catch (ExpressionRuntimeException expressionRuntimeException) {
log.error("Alert Define Rule: {} Run Error: {}.", expr, expressionRuntimeException.getMessage());
} catch (Exception e) {
log.error("Alert Define Rule: {} Run Error: {}.", e, e.getMessage());
}
return match;
}
private void handlerAvailableMetrics(long monitorId, String app, String metrics, CollectRep.MetricsData metricsData) {
if (metricsData.getCode() != CollectRep.Code.SUCCESS) {
// Collection and abnormal
......
......@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
/**
......@@ -72,7 +73,7 @@ public class AviatorConfiguration {
});
AviatorEvaluator.getInstance().addFunction(new StrContainsFunction());
AviatorEvaluator.getInstance().addFunction(new StrExistsFunction());
AviatorEvaluator.getInstance().addFunction(new ObjectExistsFunction());
AviatorEvaluator.getInstance().addFunction(new StrMatchesFunction());
}
......@@ -125,20 +126,21 @@ public class AviatorConfiguration {
}
/**
* 自定义aviator判断环境中是否存在字符串
* 自定义aviator判断环境中此对象是否存在值
*/
private static class StrExistsFunction extends AbstractFunction {
private static class ObjectExistsFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg) {
if (arg == null) {
return AviatorBoolean.FALSE;
}
Object keyTmp = arg.getValue(env);
if (keyTmp == null) {
if (Objects.isNull(keyTmp)) {
return AviatorBoolean.FALSE;
} else {
String key = String.valueOf(keyTmp);
return AviatorBoolean.valueOf(StringUtils.isNotEmpty(key));
}
String key = String.valueOf(keyTmp);
return AviatorBoolean.valueOf(env.containsKey(key));
}
@Override
public String getName() {
......
......@@ -55,11 +55,19 @@ class AviatorConfigurationTest {
// test StrExistsFunction
String expr6 = "exists('DNE_Key1')";
Boolean res6 = (Boolean) AviatorEvaluator.compile(expr6).execute(env);
Assertions.assertFalse(res6);
Assertions.assertTrue(res6);
String expr7 = "exists('k6')";
String expr7 = "exists(k6)";
Boolean res7 = (Boolean) AviatorEvaluator.compile(expr7).execute(env);
Assertions.assertTrue(res7);
Assertions.assertFalse(res7);
String expr21 = "exists('k5')";
Boolean res21 = (Boolean) AviatorEvaluator.compile(expr21).execute(env);
Assertions.assertTrue(res21);
String expr22 = "exists(k5)";
Boolean res22 = (Boolean) AviatorEvaluator.compile(expr22).execute(env);
Assertions.assertTrue(res22);
// test StrMatchesFunction
String regex1 = "'^[a-zA-Z0-9]+$'"; // only alphanumeric
......@@ -88,4 +96,4 @@ class AviatorConfigurationTest {
Boolean res13 = (Boolean) AviatorEvaluator.compile(expr10).execute(env);
Assertions.assertFalse(res13);
}
}
\ No newline at end of file
}
......@@ -121,7 +121,9 @@ public class MetricsDataController {
dataBuilder.fields(fields);
List<ValueRow> valueRows = storageData.getValuesList().stream().map(redisValueRow ->
ValueRow.builder().instance(redisValueRow.getInstance())
.values(redisValueRow.getColumnsList().stream().map(Value::new).collect(Collectors.toList()))
.values(redisValueRow.getColumnsList().stream()
.map(origin -> CommonConstants.NULL_VALUE.equals(origin) ? new Value()
: new Value(origin)).collect(Collectors.toList()))
.build()).collect(Collectors.toList());
dataBuilder.valueRows(valueRows);
return ResponseEntity.ok().body(new Message<>(dataBuilder.build()));
......@@ -165,7 +167,7 @@ public class MetricsDataController {
if (history == null) {
history = "6h";
}
Map<String, List<Value>> instanceValuesMap = null;
Map<String, List<Value>> instanceValuesMap;
if (interval == null || !interval) {
instanceValuesMap = historyDataStorage.getHistoryMetricData(monitorId, app, metrics, metric, instance, history);
} else {
......
......@@ -182,6 +182,12 @@
<nz-list-item *ngIf="currentMetrics.length > 1">
<code>instance : {{ 'alert.setting.target.instance' | i18n }}</code>
</nz-list-item>
<nz-list-item>
<code>system_value_row_count : {{ 'alert.setting.target.system_value_row_count' | i18n }}</code>
<nz-tag [nzColor]="'success'">
{{ 'alert.setting.number' | i18n }}
</nz-tag>
</nz-list-item>
<nz-list-item>
<code>
{{ 'alert.setting.operator' | i18n }} : equals(str1,str2), contains(str1,str2), exists(keyName), matches(str,regex), ==,
......@@ -227,8 +233,13 @@
[nzDropdownMatchSelectWidth]="false"
[nzPlaceHolder]="'alert.setting.rule.metric.place-holder' | i18n"
>
<nz-option *ngFor="let item of currentMetrics" [nzValue]="item" [nzLabel]="item.value" nzCustomContent>
{{ item.value }}
<nz-option
*ngFor="let item of currentMetrics"
[nzValue]="item"
[nzLabel]="item.label ? item.label : item.value"
nzCustomContent
>
{{ item.label ? item.label : item.value }}
<nz-tag [nzColor]="item.type === 0 ? 'success' : 'processing'">
{{ item.type === 0 ? ('alert.setting.number' | i18n) : ('alert.setting.string' | i18n) }}
</nz-tag>
......@@ -243,6 +254,7 @@
nz-col
nzSpan="4"
[nzShowArrow]="false"
[nzDropdownMatchSelectWidth]="false"
style="text-align: center; font-weight: bolder"
[nzDropdownStyle]="{ 'text-align': 'center', 'font-weight': 'bolder', 'font-size': 'larger' }"
[nzPlaceHolder]="'alert.setting.rule.operator' | i18n"
......@@ -283,14 +295,19 @@
[nzValue]="'!matches'"
[nzLabel]="'alert.setting.rule.operator.str-no-matches' | i18n"
></nz-option>
<nz-option [nzValue]="'exists'" [nzLabel]="'alert.setting.rule.operator.exists' | i18n"></nz-option>
<nz-option [nzValue]="'!exists'" [nzLabel]="'alert.setting.rule.operator.no-exists' | i18n"></nz-option>
</nz-select>
<input
nz-input
[disabled]="alertRule.operator == 'exists' || alertRule.operator == '!exists'"
[type]="alertRule.metric?.type === 0 ? 'number' : 'text'"
[(ngModel)]="alertRule.value"
[ngModelOptions]="{ standalone: true }"
[placeholder]="
alertRule.metric?.type === 0
alertRule.operator == 'exists' || alertRule.operator == '!exists'
? ''
: alertRule.metric?.type === 0
? ('alert.setting.rule.numeric-value.place-holder' | i18n)
: ('alert.setting.rule.string-value.place-holder' | i18n)
"
......
......@@ -298,6 +298,11 @@ export class AlertSettingComponent implements OnInit {
metrics.children.forEach(item => {
this.currentMetrics.push(item);
});
this.currentMetrics.push({
value: 'system_value_row_count',
type: 0,
label: this.i18nSvc.fanyi('alert.setting.target.system_value_row_count')
});
}
}
});
......@@ -324,15 +329,19 @@ export class AlertSettingComponent implements OnInit {
}
calculateAlertRuleExpr() {
let rules = this.alertRules.filter(rule => rule.metric != undefined && rule.operator != undefined && rule.value != undefined);
let rules = this.alertRules.filter(rule => rule.metric != undefined && rule.operator != undefined);
let index = 0;
let expr = '';
rules.forEach(rule => {
let ruleStr = '';
if (rule.metric.type === 0) {
ruleStr = `${rule.metric.value} ${rule.operator} ${rule.value} `;
} else if (rule.metric.type === 1) {
ruleStr = `${rule.operator}(${rule.metric.value},"${rule.value}")`;
if (rule.operator == 'exists' || rule.operator == '!exists') {
ruleStr = `${rule.operator}(${rule.metric.value})`;
} else {
if (rule.metric.type === 0) {
ruleStr = `${rule.metric.value} ${rule.operator} ${rule.value} `;
} else if (rule.metric.type === 1) {
ruleStr = `${rule.operator}(${rule.metric.value},"${rule.value}")`;
}
}
if (ruleStr != '') {
expr = expr + ruleStr;
......@@ -358,19 +367,25 @@ export class AlertSettingComponent implements OnInit {
let exprArr: string[] = expr.split('&&');
for (let index in exprArr) {
let exprStr = exprArr[index].trim();
if (exprStr.startsWith('!equals') || exprStr.startsWith('equals')) {
const twoParamExpressionArr = ['equals', '!equals', 'contains', '!contains', 'matches', '!matches'];
const oneParamExpressionArr = ['exists', '!exists'];
let findIndexInTowParamExpression = twoParamExpressionArr.findIndex(value => exprStr.startsWith(value));
let findIndexInOneParamExpression = oneParamExpressionArr.findIndex(value => exprStr.startsWith(value));
if (findIndexInTowParamExpression >= 0) {
let tmp = exprStr.substring(exprStr.indexOf('(') + 1, exprStr.length - 1);
let tmpArr = tmp.split(',');
if (tmpArr.length == 2) {
let metric = this.currentMetrics.find(item => item.value == tmpArr[0].trim());
let value = tmpArr[1].substring(1, tmpArr[1].length - 1);
if (exprStr.startsWith('!')) {
let rule = { metric: metric, operator: '!equals', value: value };
this.alertRules.push(rule);
} else {
let rule = { metric: metric, operator: 'equals', value: value };
this.alertRules.push(rule);
}
let rule = { metric: metric, operator: twoParamExpressionArr[findIndexInTowParamExpression], value: value };
this.alertRules.push(rule);
}
} else if (findIndexInOneParamExpression >= 0) {
let tmp = exprStr.substring(exprStr.indexOf('(') + 1, exprStr.length - 1);
if (tmp != '' && tmp != null) {
let metric = this.currentMetrics.find(item => item.value == tmp.trim());
let rule = { metric: metric, operator: oneParamExpressionArr[findIndexInOneParamExpression] };
this.alertRules.push(rule);
}
} else {
let values = exprStr.trim().split(' ');
......
......@@ -9,6 +9,7 @@
<tr *ngFor="let valueRow of smallTable.data">
<td *ngFor="let value of valueRow.values; let i = index">
{{ value.origin }}
<nz-tag *ngIf="value.origin == null" nzColor="warning">{{ 'monitors.detail.value.null' | i18n }}</nz-tag>
<nz-tag *ngIf="fields[i].unit" nzColor="success">{{ fields[i].unit }}</nz-tag>
</td>
</tr>
......@@ -29,6 +30,7 @@
<td>{{ field.name }}</td>
<td
>{{ rowValues[i].origin }}
<nz-tag *ngIf="rowValues[i].origin == null" nzColor="warning">{{ 'monitors.detail.value.null' | i18n }}</nz-tag>
<nz-tag *ngIf="field.unit" nzColor="success">{{ field.unit }}</nz-tag>
</td>
</tr>
......
......@@ -126,6 +126,8 @@
"alert.setting.rule.operator.str-no-contains": "not contains",
"alert.setting.rule.operator.str-matches": "matches",
"alert.setting.rule.operator.str-no-matches": "not matches",
"alert.setting.rule.operator.exists": "value exists",
"alert.setting.rule.operator.no-exists": "no value exists",
"alert.setting.rule.string-value.place-holder": "Please input string",
"alert.setting.rule.numeric-value.place-holder": "Please input numeric",
"alert.setting.times": "Trigger Times",
......@@ -154,6 +156,7 @@
"alert.setting.target.tip": "The selected metric object",
"alert.setting.target.other": "Other metric objects of the row",
"alert.setting.target.instance": "Instance of the row",
"alert.setting.target.system_value_row_count": "System-Metrics value row count",
"alert.setting.operator": "Supported operator functions",
"alert.silence.new": "New Silence Strategy",
"alert.silence.edit": "Edit Silence Strategy",
......@@ -336,6 +339,7 @@
"monitors.detail.chart.query-3m": "Query 3 Month",
"monitors.detail.chart.no-data": "No Metrics Data",
"monitors.detail.chart.unit": "Unit",
"monitors.detail.value.null": "No Value",
"common.name": "Name",
"common.value": "Value",
"common.search": "Search",
......
......@@ -126,6 +126,8 @@
"alert.setting.rule.operator.str-no-contains": "不包含",
"alert.setting.rule.operator.str-matches": "匹配",
"alert.setting.rule.operator.str-no-matches": "不匹配",
"alert.setting.rule.operator.exists": "存在值",
"alert.setting.rule.operator.no-exists": "不存在值",
"alert.setting.rule.string-value.place-holder": "请输入匹配字符串",
"alert.setting.rule.numeric-value.place-holder": "请输入匹配数值",
"alert.setting.times": "触发次数",
......@@ -154,6 +156,7 @@
"alert.setting.target.tip": "选中的指标对象",
"alert.setting.target.other": "所属行其它指标对象",
"alert.setting.target.instance": "所属行实例",
"alert.setting.target.system_value_row_count": "系统-指标值行数量",
"alert.setting.operator": "支持操作符函数",
"alert.silence.new": "新增静默策略",
"alert.silence.edit": "编辑静默策略",
......@@ -338,6 +341,7 @@
"monitors.detail.chart.query-3m": "查询近3月",
"monitors.detail.chart.no-data": "暂无数据",
"monitors.detail.chart.unit": "单位",
"monitors.detail.value.null": "无数据",
"common.name": "名称",
"common.value": "值",
"common.search": "搜索",
......
......@@ -126,6 +126,8 @@
"alert.setting.rule.operator.str-no-contains": "不包含",
"alert.setting.rule.operator.str-matches": "匹配",
"alert.setting.rule.operator.str-no-matches": "不匹配",
"alert.setting.rule.operator.exists": "存在值",
"alert.setting.rule.operator.no-exists": "不存在值",
"alert.setting.rule.string-value.place-holder": "請輸入匹配字符串",
"alert.setting.rule.numeric-value.place-holder": "請輸入匹配數值",
"alert.setting.times": "觸發次數",
......@@ -154,6 +156,7 @@
"alert.setting.target.tip": "選中的指標對象",
"alert.setting.target.other": "所屬行其它指標對象",
"alert.setting.target.instance": "所屬行實例",
"alert.setting.target.system_value_row_count": "系統-指標值行數量",
"alert.setting.operator": "支持操作符函數",
"alert.silence.new": "新增靜默策略",
"alert.silence.edit": "編輯靜默策略",
......@@ -336,6 +339,7 @@
"monitors.detail.chart.query-3m": "查詢近3月",
"monitors.detail.chart.no-data": "暫無數據",
"monitors.detail.chart.unit": "單位",
"monitors.detail.value.null": "無數據",
"common.name": "名稱",
"common.value": "值",
"common.search": "搜索",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册