From d4f628a801f956a426801523554ee1d7d2a0718c Mon Sep 17 00:00:00 2001 From: Chen Yang <1597081640@qq.com> Date: Mon, 21 Apr 2025 00:51:06 +0800 Subject: [PATCH] [bugfix] support no-whitespace alert expressions in instance filtering --- .../calculate/RealTimeAlertCalculator.java | 29 ++++++- .../RealTimeAlertCalculatorTest.java | 83 +++++++++++++++++++ 2 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculatorTest.java diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculator.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculator.java index 35b03342704..0db6bb15cce 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculator.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculator.java @@ -44,6 +44,7 @@ import org.apache.hertzbeat.common.queue.CommonDataQueue; import org.apache.hertzbeat.common.util.CommonUtil; import org.apache.hertzbeat.common.util.JexlExpressionRunner; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -72,7 +73,7 @@ public class RealTimeAlertCalculator { private static final Pattern APP_PATTERN = Pattern.compile("equals\\(__app__,\"([^\"]+)\"\\)"); private static final Pattern AVAILABLE_PATTERN = Pattern.compile("equals\\(__available__,\"([^\"]+)\"\\)"); private static final Pattern LABEL_PATTERN = Pattern.compile("contains\\(__labels__,\\s*\"([^\"]+)\"\\)"); - private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\\s\"(\\d+)\"\\)"); + private static final Pattern INSTANCE_PATTERN = Pattern.compile("equals\\(__instance__,\\s*\"(\\d+)\"\\)"); private static final Pattern METRICS_PATTERN = Pattern.compile("equals\\(__metrics__,\"([^\"]+)\"\\)"); private final AlerterWorkerPool workerPool; @@ -81,18 +82,38 @@ public class RealTimeAlertCalculator { private final AlarmCommonReduce alarmCommonReduce; private final AlarmCacheManager alarmCacheManager; + @Autowired public RealTimeAlertCalculator(AlerterWorkerPool workerPool, CommonDataQueue dataQueue, AlertDefineService alertDefineService, SingleAlertDao singleAlertDao, AlarmCommonReduce alarmCommonReduce, AlarmCacheManager alarmCacheManager) { + this(workerPool, dataQueue, alertDefineService, singleAlertDao, alarmCommonReduce, alarmCacheManager, true); + } + + /** + * Constructor for RealTimeAlertCalculator with a toggle to control whether to start alert calculation threads. + * + * @param workerPool The worker pool used for concurrent alert calculation. + * @param dataQueue The queue from which metric data is pulled and pushed. + * @param alertDefineService The service providing alert definition rules. + * @param singleAlertDao The DAO for fetching persisted alert states from storage. + * @param alarmCommonReduce The component responsible for reducing and sending alerts. + * @param start If true, the alert calculation threads will start automatically; + * set to false to disable thread start (useful for unit testing). + */ + public RealTimeAlertCalculator(AlerterWorkerPool workerPool, CommonDataQueue dataQueue, + AlertDefineService alertDefineService, SingleAlertDao singleAlertDao, + AlarmCommonReduce alarmCommonReduce, AlarmCacheManager alarmCacheManager, boolean start) { this.workerPool = workerPool; this.dataQueue = dataQueue; this.alarmCommonReduce = alarmCommonReduce; this.alertDefineService = alertDefineService; this.alarmCacheManager = alarmCacheManager; - startCalculate(); + if (start) { + startCalculate(); + } } - private void startCalculate() { + public void startCalculate() { Runnable runnable = () -> { while (!Thread.currentThread().isInterrupted()) { try { @@ -252,7 +273,7 @@ private void calculate(CollectRep.MetricsData metricsData) { * @param priority Current priority * @return Filtered alert definitions */ - private List filterThresholdsByAppAndMetrics(List thresholds, String app, String metrics, Map labels, String instance, int priority) { + public List filterThresholdsByAppAndMetrics(List thresholds, String app, String metrics, Map labels, String instance, int priority) { return thresholds.stream() .filter(define -> { if (StringUtils.isBlank(define.getExpr())) { diff --git a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculatorTest.java b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculatorTest.java new file mode 100644 index 00000000000..fca92c73160 --- /dev/null +++ b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/RealTimeAlertCalculatorTest.java @@ -0,0 +1,83 @@ +package org.apache.hertzbeat.alert.calculate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.hertzbeat.alert.AlerterWorkerPool; +import org.apache.hertzbeat.alert.dao.SingleAlertDao; +import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce; +import org.apache.hertzbeat.alert.service.AlertDefineService; +import org.apache.hertzbeat.common.entity.alerter.AlertDefine; +import org.apache.hertzbeat.common.queue.CommonDataQueue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +class RealTimeAlertCalculatorTest { + + private RealTimeAlertCalculator calculator; + + @BeforeEach + void setUp() { + AlerterWorkerPool mockPool = Mockito.mock(AlerterWorkerPool.class); + CommonDataQueue mockQueue = Mockito.mock(CommonDataQueue.class); + AlertDefineService mockAlertDefineService = Mockito.mock(AlertDefineService.class); + SingleAlertDao mockDao = Mockito.mock(SingleAlertDao.class); + AlarmCommonReduce mockReduce = Mockito.mock(AlarmCommonReduce.class); + AlarmCacheManager alarmCacheManager = Mockito.mock(AlarmCacheManager.class); + + Mockito.when(mockDao.querySingleAlertsByStatus(Mockito.anyString())) + .thenReturn(Collections.emptyList()); + + calculator = new RealTimeAlertCalculator(mockPool, mockQueue, mockAlertDefineService, mockDao, mockReduce, alarmCacheManager, false); + } + + @Test + void testFilterThresholdsByAppAndMetrics_withInstanceExpr_HasSpace() { + String app = "redis"; + String instanceId = "501045327364864"; + int priority = 0; + + AlertDefine matchDefine = new AlertDefine(); + matchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")"); + + AlertDefine unmatchDefine = new AlertDefine(); + unmatchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__, \"999999999\")"); + + List allDefines = Arrays.asList(matchDefine, unmatchDefine); + + List filtered = calculator.filterThresholdsByAppAndMetrics(allDefines, app, "", Map.of(), instanceId, priority); + + // It should filter out 999999999. + assertEquals(1, filtered.size()); + assertEquals("equals(__app__,\"redis\") && equals(__instance__, \"501045327364864\")", + filtered.get(0).getExpr()); + } + + @Test + void testFilterThresholdsByAppAndMetrics_withInstanceExpr_NoSpace() { + String app = "redis"; + String instanceId = "501045327364864"; + int priority = 0; + + AlertDefine matchDefine = new AlertDefine(); + matchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__,\"501045327364864\")"); + + AlertDefine unmatchDefine = new AlertDefine(); + unmatchDefine.setExpr("equals(__app__,\"redis\") && equals(__instance__,\"999999999\")"); + + List allDefines = Arrays.asList(matchDefine, unmatchDefine); + + List filtered = calculator.filterThresholdsByAppAndMetrics(allDefines, app, "", Map.of(), instanceId, priority); + + // It should filter out 999999999. + assertEquals(1, filtered.size()); + assertEquals("equals(__app__,\"redis\") && equals(__instance__,\"501045327364864\")", + filtered.get(0).getExpr()); + } +}