This is an automated email from the ASF dual-hosted git repository.

liuhongyu 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 256302fdcf [fix] Fix antlr4 parsing of `or` and `unless` logical and 
set binary operators (#3488)
256302fdcf is described below

commit 256302fdcf0b05ca5d9195e7b5f731c99e1501fb
Author: Duansg <[email protected]>
AuthorDate: Sun Jun 22 19:46:34 2025 +0800

    [fix] Fix antlr4 parsing of `or` and `unless` logical and set binary 
operators (#3488)
    
    Co-authored-by: wordofwordof <[email protected]>
    Co-authored-by: aias00 <[email protected]>
---
 .../alert/expr/AlertExpressionEvalVisitor.java     | 107 ++++-------
 .../alert/expr/AlertExpressionEvalVisitorTest.java | 198 +++++++++++++++++++--
 .../alert/service/DataSourceServiceTest.java       |  17 +-
 3 files changed, 227 insertions(+), 95 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 31eb63f592..2056d0343c 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
@@ -115,92 +115,51 @@ public class AlertExpressionEvalVisitor extends 
AlertExpressionBaseVisitor<List<
         List<Map<String, Object>> leftOperand = visit(ctx.left);
         List<Map<String, Object>> rightOperand = visit(ctx.right);
 
-        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;
-            }
-            if (item.get(VALUE) != null) {
-                rightMap = item;
-                rightMatch = true;
-                break;
+        // build a hashMap of the left-hand label collection
+        Map<String, Map<String, Object>> leftLabelMap = leftOperand.stream()
+                .filter(item -> item.get(VALUE) != null)
+                .collect(Collectors.toMap(this::labelKey, HashMap::new, (k1, 
k2) -> k1));
+
+        // first add all the non-empty items on the left side
+        List<Map<String, Object>> results = new 
ArrayList<>(leftLabelMap.values());
+
+        // add the term that has a value on the right side and not on the left 
side
+        for (Map<String, Object> rightItem : rightOperand) {
+            Object rightVal = rightItem.get(VALUE);
+            if (rightVal == null) {
+                continue;
             }
-        }
-        if (leftMatch && rightMatch) {
-            rightMap.putAll(leftMap);
-            return new LinkedList<>(List.of(rightMap));
-        } else if (leftMatch) {
-            return new LinkedList<>(List.of(leftMap));
-        } else if (rightMatch) {
-            return new LinkedList<>(List.of(rightMap));
-        } else {
-            if (leftMap != null && rightMap != null) {
-                rightMap.putAll(leftMap);
-                return new LinkedList<>(List.of(rightMap));
-            } else if (leftMap != null) {
-                return new LinkedList<>(List.of(leftMap));
-            } else if (rightMap != null) {
-                return new LinkedList<>(List.of(rightMap));
+            String key = labelKey(rightItem);
+            if (!leftLabelMap.containsKey(key)) {
+                results.add(new HashMap<>(rightItem));
             }
         }
-        return new LinkedList<>();
+        return results;
     }
 
     @Override
     public List<Map<String, Object>> 
visitUnlessExpr(AlertExpressionParser.UnlessExprContext ctx) {
         List<Map<String, Object>> leftOperand = visit(ctx.left);
         List<Map<String, Object>> rightOperand = visit(ctx.right);
-        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;
-            }
-            if (item.get(VALUE) != null) {
-                rightMap = item;
-                rightMatch = true;
-                break;
+        List<Map<String, Object>> results = new ArrayList<>();
+
+        // build a hash set of the right-side tag collection
+        Set<String> rightLabelSet = 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 (leftMatch && !rightMatch) {
-            return new LinkedList<>(List.of(leftMap));
-        } 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));
-                }
+            if (!rightLabelSet.contains(labelKey(leftItem))) {
+                results.add(new HashMap<>(leftItem));
             }
         }
-        return new LinkedList<>();
+        return results;
     }
 
     @Override
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 7be155fcf6..2ee048e1d6 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
@@ -24,12 +24,14 @@ 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 static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
 
 /**
@@ -240,11 +242,187 @@ class AlertExpressionEvalVisitorTest {
         assertEquals(10.0, result.get(0).get("__value__"));
     }
 
+    @Test
+    void testUnlessOpPromql() {
+        String promql = "http_server_requests_seconds_count > 10 unless 
http_server_requests_seconds_max > 0";
+
+        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> 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/health");
+                put("outcome", "SUCCESS");
+                put("status", "200");
+            }
+        };
+
+        
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1));
+        
when(mockExecutor.execute("http_server_requests_seconds_max")).thenReturn(List.of(maxValue1));
+        List<Map<String, Object>> result = evaluate(promql);
+        assertEquals(1, result.size());
+        assertEquals(1307, result.get(0).get("__value__"));
+
+        maxValue1.put("uri", "/actuator/prometheus");
+        result = evaluate(promql);
+        assertEquals(0, result.size());
+
+        Map<String, Object> sumValue1 = new HashMap<>() {
+            {
+                put("exception", "none");
+                put("instance", "host.docker.internal:8989");
+                put("__value__", 20.018);
+                put("method", "GET");
+                put("__name__", "http_server_requests_seconds_sum");
+                put("__timestamp__", "1.750320922467E9");
+                put("error", "none");
+                put("job", "spring-boot-app");
+                put("uri", "/**");
+                put("outcome", "SUCCESS");
+                put("status", "200");
+            }
+        };
+
+        maxValue1.put("uri", "/actuator/health");
+        
when(mockExecutor.execute("http_server_requests_seconds_sum")).thenReturn(List.of(sumValue1));
+        promql = "(http_server_requests_seconds_count > 10 unless 
http_server_requests_seconds_max > 0) unless http_server_requests_seconds_sum > 
10";
+        result = evaluate(promql);
+        assertEquals(1, result.size());
+        assertEquals(1307, result.get(0).get("__value__"));
+    }
+
+    @Test
+    void testOrOpPromql() {
+        String promql = "http_server_requests_seconds_count > 10 or 
jvm_threads_states_threads > 0";
+
+        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> threadsValue1 = new HashMap<>() {
+            {
+                put("instance", "host.docker.internal:8989");
+                put("__value__", 10.007799125);
+                put("__name__", "jvm_threads_states_threads");
+                put("__timestamp__", "1.750320922467E9");
+                put("job", "spring-boot-app");
+                put("state=", "runnable");
+            }
+        };
+
+        Map<String, Object> threadsValue2 = new HashMap<>() {
+            {
+                put("instance", "host.docker.internal:8989");
+                put("__value__", 1);
+                put("__name__", "jvm_threads_states_threads");
+                put("__timestamp__", "1.750320922467E9");
+                put("job", "spring-boot-app");
+                put("state=", "timed-waiting");
+            }
+        };
+
+        Map<String, Object> threadsValue3 = new HashMap<>() {
+            {
+                put("instance", "host.docker.internal:8989");
+                put("__value__", 19.02);
+                put("__name__", "jvm_threads_states_threads");
+                put("__timestamp__", "1.750320922467E9");
+                put("job", "spring-boot-app");
+                put("state=", "waiting");
+            }
+        };
+
+        
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(List.of(countValue1,
 countValue2, countValue3));
+        
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(List.of(threadsValue1,
 threadsValue2, threadsValue3));
+        List<Map<String, Object>> result = evaluate(promql);
+        assertEquals(5, result.size());
+        assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
+
+
+        
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(new 
ArrayList<>());
+        result = evaluate(promql);
+        assertEquals(2, result.size());
+        assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
+        assertEquals(1307, result.get(0).get("__value__"));
+
+        
when(mockExecutor.execute("http_server_requests_seconds_count")).thenReturn(new 
ArrayList<>());
+        
when(mockExecutor.execute("jvm_threads_states_threads")).thenReturn(List.of(threadsValue1,
 threadsValue2, threadsValue3));
+        result = evaluate(promql);
+        assertEquals(3, result.size());
+        assertTrue(result.stream().allMatch(t -> null != t.get("__value__")));
+        assertEquals(10.007799125, result.get(0).get("__value__"));
+    }
+
     @Test
     void testMultipleUnlessConditions() {
-        when(mockExecutor.execute("metric1")).thenReturn(List.of(new 
HashMap<>(Map.of("__value__", 40.0))));
-        when(mockExecutor.execute("metric2")).thenReturn(List.of(new 
HashMap<>(Map.of("__value__", 50.0))));
-        when(mockExecutor.execute("metric3")).thenReturn(List.of(new 
HashMap<>(Map.of("__value__", 60.0))));
+        when(mockExecutor.execute("metric1")).thenReturn(List.of(new 
HashMap<>(Map.of("job", "api", "__value__", 40.0))));
+        when(mockExecutor.execute("metric2")).thenReturn(List.of(new 
HashMap<>(Map.of("job", "web", "__value__", 50.0))));
+        when(mockExecutor.execute("metric3")).thenReturn(List.of(new 
HashMap<>(Map.of("job", "api", "__value__", 60.0))));
         when(mockExecutor.execute("select cpu_usage from metrics where service 
= 'web'")).thenReturn(
                 List.of(new HashMap<>(Map.of("__value__", 40.0))));
         when(mockExecutor.execute("select memory_usage from metrics where 
service = 'db'")).thenReturn(
@@ -252,15 +430,13 @@ class AlertExpressionEvalVisitorTest {
         when(mockExecutor.execute("select disk_usage from metrics where 
service = 'cache'")).thenReturn(
                 List.of(new HashMap<>(Map.of("__value__", 60.0))));
         // promql
-        List<Map<String, Object>> result = evaluate("metric1 > 30 unless 
metric2 > 45 unless metric3 < 70");
-        assertEquals(1, result.size());
-        assertNull(result.get(0).get("__value__"));
+        List<Map<String, Object>> result = evaluate("(metric1 > 30 unless 
metric2 > 45) unless metric3 > 50");
+        assertEquals(0, result.size());
         // sql
-        result = evaluate("(select cpu_usage from metrics where service = 
'web') > 30"
-                + " unless (select memory_usage from metrics where service = 
'db') > 45"
-                + " unless (select disk_usage from metrics where service = 
'cache') < 70");
-        assertEquals(1, result.size());
-        assertNull(result.get(0).get("__value__"));
+        result = evaluate("((select cpu_usage from metrics where service = 
'web') > 30"
+                + " unless (select memory_usage from metrics where service = 
'db') > 55)"
+                + " unless (select disk_usage from metrics where service = 
'cache') > 50");
+        assertEquals(0, result.size());
     }
 
     @Test
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 f4e35cf21f..25e5100ee4 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
@@ -392,8 +392,7 @@ class DataSourceServiceTest {
         dataSourceService.setExecutors(List.of(mockExecutor));
 
         List<Map<String, Object>> result = 
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 
250 or node_cpu_seconds_total{mode=\"idle\"} < 20");
-        assertEquals(1, result.size());
-        assertNull(result.get(0).get("__value__"));
+        assertEquals(0, result.size());
     }
 
     @Test
@@ -414,8 +413,7 @@ class DataSourceServiceTest {
         dataSourceService.setExecutors(List.of(mockExecutor));
 
         List<Map<String, Object>> result = 
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 
50 or node_cpu_seconds_total{mode=\"idle\"} < 320");
-        assertEquals(1, result.size());
-        assertNotNull(result.get(0).get("__value__"));
+        assertEquals(4, result.size());
     }
 
     @Test
@@ -436,8 +434,8 @@ class DataSourceServiceTest {
         dataSourceService.setExecutors(List.of(mockExecutor));
 
         List<Map<String, Object>> result = 
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 
50 unless node_cpu_seconds_total{mode=\"idle\"} < 320");
-        assertEquals(1, result.size());
-        assertNull(result.get(0).get("__value__"));
+        assertEquals(2, result.size());
+        assertEquals(100.0, result.get(0).get("__value__"));
     }
 
     @Test
@@ -458,8 +456,8 @@ class DataSourceServiceTest {
         dataSourceService.setExecutors(List.of(mockExecutor));
 
         List<Map<String, Object>> result = 
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 
50 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
-        assertEquals(1, result.size());
-        assertNotNull(result.get(0).get("__value__"));
+        assertEquals(2, result.size());
+        assertEquals(100.0, result.get(0).get("__value__"));
     }
 
     @Test
@@ -480,8 +478,7 @@ class DataSourceServiceTest {
         dataSourceService.setExecutors(List.of(mockExecutor));
 
         List<Map<String, Object>> result = 
dataSourceService.calculate("promql", "node_cpu_seconds_total{mode=\"user\"} > 
250 unless node_cpu_seconds_total{mode=\"idle\"} < 20");
-        assertEquals(1, result.size());
-        assertNull(result.get(0).get("__value__"));
+        assertEquals(0, result.size());
     }
 
     @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to