This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 1ebfe76c37 [fix] evaluate threshold rules with empty field values
(#4307)
1ebfe76c37 is described below
commit 1ebfe76c37ae4b8536c9f914384e5184c5e9ed7f
Author: NekoPunch <[email protected]>
AuthorDate: Sun Aug 16 20:05:32 2026 -0700
[fix] evaluate threshold rules with empty field values (#4307)
Co-authored-by: aias00 <[email protected]>
---
.../realtime/MetricsRealTimeAlertCalculator.java | 16 ++--
.../MetricsRealTimeAlertCalculatorMatchTest.java | 88 ++++++++++++++++++++++
2 files changed, 93 insertions(+), 11 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculator.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculator.java
index f28a48a5d2..126041ca69 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculator.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculator.java
@@ -251,20 +251,14 @@ public class MetricsRealTimeAlertCalculator {
}
final int fieldType = field.getType();
+ // strict jexl aborts the whole rule on undefined
variables,
+ // so define every field even when its value is empty or
unparseable
if (fieldType == CommonConstants.TYPE_NUMBER) {
- final Double doubleValue;
- if ((doubleValue =
CommonUtil.parseStrDouble(valueStr)) != null) {
- fieldValueMap.put(field.getName(), doubleValue);
- }
+ fieldValueMap.put(field.getName(),
CommonUtil.parseStrDouble(valueStr));
} else if (fieldType == CommonConstants.TYPE_TIME) {
- final Integer integerValue;
- if ((integerValue =
CommonUtil.parseStrInteger(valueStr)) != null) {
- fieldValueMap.put(field.getName(), integerValue);
- }
+ fieldValueMap.put(field.getName(),
CommonUtil.parseStrInteger(valueStr));
} else {
- if (StringUtils.isNotEmpty(valueStr)) {
- fieldValueMap.put(field.getName(), valueStr);
- }
+ fieldValueMap.put(field.getName(), valueStr);
}
if (field.getLabel()) {
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculatorMatchTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculatorMatchTest.java
index 6844bb1bcb..44929a5d29 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculatorMatchTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/realtime/MetricsRealTimeAlertCalculatorMatchTest.java
@@ -27,11 +27,13 @@ import
org.apache.hertzbeat.alert.service.AlertDefineService;
import org.apache.hertzbeat.common.constants.CommonConstants;
import org.apache.hertzbeat.common.constants.MetricDataConstants;
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
+import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.queue.CommonDataQueue;
import org.apache.hertzbeat.common.queue.impl.InMemoryCommonDataQueue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -43,6 +45,7 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -254,4 +257,89 @@ public class MetricsRealTimeAlertCalculatorMatchTest {
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any());
}
+ @Test
+ void testEmptyStringFieldStillTriggersAlert() throws InterruptedException {
+ CollectRep.MetricsData.Builder builder =
CollectRep.MetricsData.newBuilder();
+ builder.setId(518679137103104L)
+ .setApp("fullsite")
+ .setMetrics("summary")
+ .setPriority(1)
+ .setCode(CollectRep.Code.SUCCESS);
+
+ CollectRep.Field url =
CollectRep.Field.newBuilder().setName("url").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
+ CollectRep.Field statusCode =
CollectRep.Field.newBuilder().setName("statusCode").setType(CommonConstants.TYPE_STRING).build();
+ CollectRep.Field errorMsg =
CollectRep.Field.newBuilder().setName("errorMsg").setType(CommonConstants.TYPE_STRING).build();
+
+ Map<String, String> meta = new HashMap<>();
+ meta.put(MetricDataConstants.INSTANCE_NAME, "site");
+ meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
+
+ builder.addMetadataAll(meta);
+ builder.addAllFields(Lists.newArrayList(url, statusCode, errorMsg));
+ builder.addValueRow(CollectRep.ValueRow.newBuilder()
+
.addColumn("https://example.com/broken").addColumn("404").addColumn("").build());
+
+ CollectRep.MetricsData metricsData = builder.build();
+
+ AlertDefine matchDefine = new AlertDefine();
+ matchDefine.setId(1L);
+ matchDefine.setName("sitemap-status");
+ matchDefine.setExpr("equals(__app__,\"fullsite\") &&
!matches(statusCode,\"^2[0-9]+\") && !contains(errorMsg,\"timed out\")");
+ matchDefine.setTemplate("site down: ${url}");
+ matchDefine.setTimes(1);
+
+
when(alertDefineService.getMetricsRealTimeAlertDefines()).thenReturn(Collections.singletonList(matchDefine));
+
when(dataQueue.pollMetricsDataToAlerter()).thenReturn(metricsData).thenThrow(new
InterruptedException());
+
+ metricsRealTimeAlertCalculator.startCalculate();
+
+ Thread.sleep(3000);
+
+ ArgumentCaptor<SingleAlert> alertCaptor =
ArgumentCaptor.forClass(SingleAlert.class);
+ verify(alarmCommonReduce,
times(1)).reduceAndSendAlarm(alertCaptor.capture());
+ assertEquals("site down: https://example.com/broken",
alertCaptor.getValue().getContent());
+ }
+
+ @Test
+ void testUnparseableNumberFieldDoesNotAbortRule() throws
InterruptedException {
+ CollectRep.MetricsData.Builder builder =
CollectRep.MetricsData.newBuilder();
+ builder.setId(518679137103105L)
+ .setApp("fullsite")
+ .setMetrics("summary")
+ .setPriority(1)
+ .setCode(CollectRep.Code.SUCCESS);
+
+ CollectRep.Field url =
CollectRep.Field.newBuilder().setName("url").setType(CommonConstants.TYPE_STRING).setLabel(true).build();
+ CollectRep.Field responseTime =
CollectRep.Field.newBuilder().setName("responseTime").setType(CommonConstants.TYPE_NUMBER).build();
+
+ Map<String, String> meta = new HashMap<>();
+ meta.put(MetricDataConstants.INSTANCE_NAME, "site");
+ meta.put(MetricDataConstants.INSTANCE, "127.0.0.1");
+
+ builder.addMetadataAll(meta);
+ builder.addAllFields(Lists.newArrayList(url, responseTime));
+ builder.addValueRow(CollectRep.ValueRow.newBuilder()
+ .addColumn("https://example.com/a").addColumn("").build());
+
+ CollectRep.MetricsData metricsData = builder.build();
+
+ AlertDefine guardedDefine = new AlertDefine();
+ guardedDefine.setId(2L);
+ guardedDefine.setName("slow-site");
+ guardedDefine.setExpr("equals(__app__,\"fullsite\") &&
exists(responseTime) && responseTime > 100");
+ guardedDefine.setTemplate("slow: ${url}");
+ guardedDefine.setTimes(1);
+
+
when(alertDefineService.getMetricsRealTimeAlertDefines()).thenReturn(Collections.singletonList(guardedDefine));
+
when(dataQueue.pollMetricsDataToAlerter()).thenReturn(metricsData).thenThrow(new
InterruptedException());
+
+ metricsRealTimeAlertCalculator.startCalculate();
+
+ Thread.sleep(3000);
+
+ // unparseable number is defined as null: exists() short-circuits to
false, no alarm and no abort
+ verify(alarmCommonReduce, never()).reduceAndSendAlarm(any());
+ verify(alarmCacheManager, times(1)).removeFiring(any(), any());
+ }
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]