This is an automated email from the ASF dual-hosted git repository.
gongchao 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 ddb1601290 [fix] antlr4 `vectors and` parse semantic fixes and
optimizations (#3482)
ddb1601290 is described below
commit ddb1601290e13fde2995a649568e560cb2925b56
Author: Duansg <[email protected]>
AuthorDate: Sat Jun 21 07:55:54 2025 +0800
[fix] antlr4 `vectors and` parse semantic fixes and optimizations (#3482)
Co-authored-by: aias00 <[email protected]>
---
.../alert/expr/AlertExpressionEvalVisitor.java | 70 +++++++------
.../alert/expr/AlertExpressionEvalVisitorTest.java | 113 ++++++++++++++++++++-
.../alert/service/DataSourceServiceTest.java | 53 +++++-----
3 files changed, 171 insertions(+), 65 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java
index 9b6c5f1c23..31eb63f592 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java
@@ -25,6 +25,8 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Alert expression visitor implement
@@ -32,7 +34,9 @@ import java.util.Map;
public class AlertExpressionEvalVisitor extends
AlertExpressionBaseVisitor<List<Map<String, Object>>> {
private static final String THRESHOLD = "__threshold__";
+ private static final String NAME = "__name__";
private static final String VALUE = "__value__";
+ private static final String TIMESTAMP = "__timestamp__";
private final QueryExecutor executor;
private final CommonTokenStream tokens;
@@ -84,42 +88,26 @@ public class AlertExpressionEvalVisitor extends
AlertExpressionBaseVisitor<List<
public List<Map<String, Object>>
visitAndExpr(AlertExpressionParser.AndExprContext ctx) {
List<Map<String, Object>> leftOperand = visit(ctx.left);
List<Map<String, Object>> rightOperand = visit(ctx.right);
+ List<Map<String, Object>> results = new ArrayList<>();
- Map<String, Object> leftMap = null;
- boolean leftMatch = false;
- Map<String, Object> rightMap = null;
- boolean rightMatch = false;
- for (Map<String, Object> item : leftOperand) {
- if (leftMap == null) {
- leftMap = item;
- }
- if (item.get(VALUE) != null) {
- leftMap = item;
- leftMatch = true;
- break;
- }
- }
- for (Map<String, Object> item : rightOperand) {
- if (rightMap == null) {
- rightMap = item;
+ // build a hash set of the right-side tag collection
+ Set<String> rightLabelsSet = rightOperand.stream()
+ .filter(item -> item.get(VALUE) != null)
+ .map(this::labelKey)
+ .collect(Collectors.toSet());
+
+ // iterate over the left side, O(1) match
+ for (Map<String, Object> leftItem : leftOperand) {
+ Object leftVal = leftItem.get(VALUE);
+ if (leftVal == null) {
+ continue;
}
- if (item.get(VALUE) != null) {
- rightMap = item;
- rightMatch = true;
- break;
+ String labelKey = labelKey(leftItem);
+ if (rightLabelsSet.contains(labelKey)) {
+ results.add(new HashMap<>(leftItem));
}
}
- if (leftMatch && rightMatch) {
- rightMap.putAll(leftMap);
- return new LinkedList<>(List.of(rightMap));
- } else if (leftMap != null) {
- leftMap.put(VALUE, null);
- return new LinkedList<>(List.of(leftMap));
- } else if (rightMap != null) {
- rightMap.put(VALUE, null);
- return new LinkedList<>(List.of(rightMap));
- }
- return new LinkedList<>();
+ return results;
}
@Override
@@ -327,4 +315,20 @@ public class AlertExpressionEvalVisitor extends
AlertExpressionBaseVisitor<List<
String script = text.substring(1, text.length() - 1);
return executor.execute(script);
}
-}
+
+ /**
+ * Generate tag key (excluding `__name__` and `__value__` and
`__timestamp__`)
+ */
+ private String labelKey(Map<String, Object> labelsMap) {
+ if (null == labelsMap || labelsMap.isEmpty()) {
+ return "-";
+ }
+ String key = labelsMap.entrySet().stream()
+ .filter(e -> !e.getKey().equals(VALUE) &&
!e.getKey().equals(NAME) && !e.getKey().equals(TIMESTAMP))
+ .sorted(Map.Entry.comparingByKey())
+ .map(e -> e.getKey() + "=" + (e.getValue() == null ? "" :
e.getValue()))
+ .collect(Collectors.joining(","));
+ return key.isEmpty() ? "-" : key;
+ }
+
+}
\ No newline at end of file
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitorTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitorTest.java
index 0951e635db..7be155fcf6 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitorTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitorTest.java
@@ -320,11 +320,11 @@ class AlertExpressionEvalVisitorTest {
List.of(new HashMap<>(Map.of("__value__", 250.0))));
when(mockExecutor.execute("select min(response_time) from api_metrics
where endpoint = '/api/users'")).thenReturn(
List.of(new HashMap<>(Map.of("__value__", 50.0))));
-
+
List<Map<String, Object>> result = evaluate("(select
max(response_time) from api_metrics where endpoint = '/api/users') > 200");
assertEquals(1, result.size());
assertEquals(250.0, result.get(0).get("__value__"));
-
+
result = evaluate("(select min(response_time) from api_metrics where
endpoint = '/api/users') < 100");
assertEquals(1, result.size());
assertEquals(50.0, result.get(0).get("__value__"));
@@ -473,6 +473,115 @@ class AlertExpressionEvalVisitorTest {
assertEquals(80, result.get(0).get("__value__"));
}
+ @Test
+ void testAndOpPromql() {
+ String promql = "http_server_requests_seconds_count > 10 and
http_server_requests_seconds_max > 5";
+
+ Map<String, Object> countValue1 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 1307);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_count");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/actuator/prometheus");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+ Map<String, Object> countValue2 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 16);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_count");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/**");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+ Map<String, Object> countValue3 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 7);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_count");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/actuator/health");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+ Map<String, Object> maxValue1 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 10.007799125);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_max");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/actuator/prometheus");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+ Map<String, Object> maxValue2 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 10);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_count");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/**");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+ Map<String, Object> maxValue3 = new HashMap<>() {
+ {
+ put("exception", "none");
+ put("instance", "host.docker.internal:8989");
+ put("__value__", 0);
+ put("method", "GET");
+ put("__name__", "http_server_requests_seconds_count");
+ put("__timestamp__", "1.750320922467E9");
+ put("error", "none");
+ put("job", "spring-boot-app");
+ put("uri", "/actuator/health");
+ put("outcome", "SUCCESS");
+ put("status", "200");
+ }
+ };
+
+
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1,
countValue2, countValue3));
+
when(mockExecutor.execute("http_server_requests_seconds_max")).thenReturn(List.of(maxValue1,
maxValue2, maxValue3));
+ List<Map<String, Object>> result = evaluate(promql);
+ assertEquals(2, result.size());
+ assertEquals(1307, result.get(0).get("__value__"));
+ assertEquals(16, result.get(1).get("__value__"));
+ }
+
+
private List<Map<String, Object>> evaluate(String expression) {
AlertExpressionLexer lexer = new
AlertExpressionLexer(CharStreams.fromString(expression));
CommonTokenStream tokens = new CommonTokenStream(lexer);
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/DataSourceServiceTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/DataSourceServiceTest.java
index dda175ce0d..f4e35cf21f 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/DataSourceServiceTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/DataSourceServiceTest.java
@@ -17,11 +17,6 @@
package org.apache.hertzbeat.alert.service;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import java.util.HashMap;
-
import com.github.benmanes.caffeine.cache.Cache;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
@@ -29,17 +24,24 @@ import
org.apache.hertzbeat.alert.service.impl.DataSourceServiceImpl;
import org.apache.hertzbeat.warehouse.db.QueryExecutor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
/**
* test case for {@link DataSourceService}
*/
class DataSourceServiceTest {
-
+
private DataSourceServiceImpl dataSourceService;
-
+
@BeforeEach
void setUp() {
dataSourceService = new DataSourceServiceImpl();
@@ -51,12 +53,12 @@ class DataSourceServiceTest {
new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554,
"instance", "node1")),
new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555,
"instance", "node2"))
);
-
+
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
Mockito.when(mockExecutor.execute(Mockito.anyString())).thenReturn(prometheusData);
dataSourceService.setExecutors(List.of(mockExecutor));
-
+
List<Map<String, Object>> result =
dataSourceService.calculate("promql", "node_cpu_seconds_total > 150");
assertEquals(2, result.size());
assertNull(result.get(0).get("__value__"));
@@ -296,45 +298,36 @@ class DataSourceServiceTest {
@Test
void calculate15() {
List<Map<String, Object>> prometheusData1 = List.of(
- new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554,
"instance", "node1")),
- new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555,
"instance", "node2"))
+ new HashMap<>(Map.of("__value__", 1))
);
- List<Map<String, Object>> prometheusData2 = List.of(
- new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554,
"instance", "node1")),
- new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555,
"instance", "node2"))
- );
-
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
-
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
-
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
+
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"user\"}
> 250)")).thenReturn(prometheusData1);
+
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"idle\"}
< 220 )")).thenReturn(new ArrayList<>());
dataSourceService.setExecutors(List.of(mockExecutor));
- List<Map<String, Object>> result =
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} >
250 and node_cpu_seconds_total{mode=\"idle\"} < 220");
- assertEquals(1, result.size());
- assertNull(result.get(0).get("__value__"));
+ List<Map<String, Object>> result =
dataSourceService.calculate("promql",
"count(node_cpu_seconds_total{mode=\"user\"} > 250) > 0 and
count(node_cpu_seconds_total{mode=\"idle\"} < 220 ) > 0");
+ assertEquals(0, result.size());
}
@Test
void calculate16() {
List<Map<String, Object>> prometheusData1 = List.of(
- new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554,
"instance", "node1")),
- new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555,
"instance", "node2"))
+ new HashMap<>(Map.of("__value__", 1))
);
List<Map<String, Object>> prometheusData2 = List.of(
- new HashMap<>(Map.of("__value__", 100.0, "timestamp", 1343554,
"instance", "node1")),
- new HashMap<>(Map.of("__value__", 200.0, "timestamp", 1343555,
"instance", "node2"))
+ new HashMap<>(Map.of("__value__", 1))
);
QueryExecutor mockExecutor = Mockito.mock(QueryExecutor.class);
Mockito.when(mockExecutor.support("promql")).thenReturn(true);
-
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"user\"}")).thenReturn(prometheusData1);
-
Mockito.when(mockExecutor.execute("node_cpu_seconds_total{mode=\"idle\"}")).thenReturn(prometheusData2);
+
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"user\"}
> 250)")).thenReturn(prometheusData1);
+
Mockito.when(mockExecutor.execute("count(node_cpu_seconds_total{mode=\"idle\"}
< 220 )")).thenReturn(prometheusData2);
dataSourceService.setExecutors(List.of(mockExecutor));
- List<Map<String, Object>> result =
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} >
50 and node_cpu_seconds_total{mode=\"idle\"} < 20");
+ List<Map<String, Object>> result =
dataSourceService.calculate("promql",
"count(node_cpu_seconds_total{mode=\"user\"} > 250) > 0 and
count(node_cpu_seconds_total{mode=\"idle\"} < 220 ) > 0");
assertEquals(1, result.size());
- assertNull(result.get(0).get("__value__"));
+ assertNotNull(result.get(0).get("__value__"));
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]