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]

Reply via email to