Skip to content

[refactor] Refactor the comparison logic #3574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Duansg
Copy link
Contributor

@Duansg Duansg commented Jul 13, 2025

What's changed?

Refactor the comparison logic in org.apache.hertzbeat.alert.expr.AlertExpressionEvalVisitor#visitComparisonExpr

For details:

  1. Optimized the logic of historical comparison binary operators.
  2. Improve the semantics of promql, add the comparison binary operators function, and add scalar and scalar, scalar and vector, and vector and vector.
  3. Antlr4 added the bool modifier.
iShot_2025-07-13_23 24 45
  1. Compatible with current alarm logic processing.
  2. Complete/fix test cases and add new test cases.

Checklist

  • I have read the Contributing Guide
  • I have written the necessary doc or comment.
  • I have added the necessary unit tests and all cases have passed.

Add or update API

  • I have added the necessary e2e tests and all cases have passed.

// vector and vector
iShot_2025-07-13_21 48 51
iShot_2025-07-13_21 49 16
iShot_2025-07-13_21 53 03

// vector and scalar
iShot_2025-07-13_21 47 27
iShot_2025-07-13_21 47 49
iShot_2025-07-13_21 48 15

// scalar and vector
iShot_2025-07-13_21 45 26
iShot_2025-07-13_21 46 00
iShot_2025-07-13_21 46 27

// scalar and scalar
iShot_2025-07-13_21 43 37
iShot_2025-07-13_21 43 51

@MasamiYui MasamiYui requested a review from Copilot July 14, 2025 09:54
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the comparison logic in AlertExpressionEvalVisitor to fully support PromQL-style comparisons with an optional bool modifier and combinations of scalar/vector operands.

  • Added optional bool modifier and distinct handling for scalar–scalar, scalar–vector, vector–scalar, and vector–vector comparisons.
  • Updated the ANTLR grammar (AlertExpression.g4) to allow an optional BOOL token in comparison expressions.
  • Added new unit tests in AlertExpressionEvalVisitorTest covering all comparison scenarios.

Reviewed Changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

File Description
hertzbeat-common/src/main/java/org/apache/hertzbeat/common/support/exception/ExpressionVisitorException.java Introduces a custom ExpressionVisitorException for parse errors
hertzbeat-alerter/src/main/resources/expr/AlertExpression.g4 Extends the comparison rule to include an optional BOOL modifier
hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java Refactors visitComparisonExpr to handle scalar/vector and bool
hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitorTest.java Updates existing tests and adds new ones for comparison expressions
Comments suppressed due to low confidence (2)

hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java:305

  • [nitpick] Method name isValidValue is misleading because it returns true for invalid values (null or List). Consider renaming to isInvalidValue or inverting the logic for clarity.
    private boolean isValidValue(Object val) {

hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/expr/AlertExpressionEvalVisitor.java:132

  • Missing import for java.util.stream.Collectors, which will cause a compile error when calling collect(Collectors.toMap(...)).
                .collect(Collectors.toMap(this::labelKey, item -> item, (existing, replacement) -> existing));

case AlertExpressionParser.GE -> left >= right;
case AlertExpressionParser.LT -> left < right;
case AlertExpressionParser.LE -> left <= right;
case AlertExpressionParser.EQ -> left == right;
Copy link
Preview

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using == to compare doubles can be unreliable due to floating‐point precision. Consider using Double.compare(left, right) == 0 or a tolerance-based comparison.

Suggested change
case AlertExpressionParser.EQ -> left == right;
case AlertExpressionParser.EQ -> Double.compare(left, right) == 0;

Copilot uses AI. Check for mistakes.

try {
return Double.parseDouble(text);
} catch (NumberFormatException e) {
throw new ExpressionVisitorException("number format exception", e);
Copy link
Preview

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message "number format exception" is too generic. Include the input text that failed to parse for better troubleshooting.

Suggested change
throw new ExpressionVisitorException("number format exception", e);
throw new ExpressionVisitorException("Failed to parse '" + text + "' as a double", e);

Copilot uses AI. Check for mistakes.

}

// scalar and vector
if (leftIsScalar) {
Copy link
Preview

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The branches for scalar–vector and vector–scalar comparisons share very similar logic. Consider extracting a helper method to reduce duplication.

Copilot uses AI. Check for mistakes.

@MasamiYui
Copy link
Member

Thank you for your PR.

There is an example I would like to discuss with you. According to the current parsing rules, http_requests_time > avg(rate(http_requests_time[5m])) will be parsed as vector > vector, but in reality, it should be vector > scalar.

At present, only LiteralExpr(number) is regarded as a scalar. I think more judgment conditions may be needed to determine whether something is a scalar, such as whether there are labels in the metric query results.

@Duansg
Copy link
Contributor Author

Duansg commented Jul 16, 2025

avg(rate(http_requests_time[5m]))

Thank you for your PR.

There is an example I would like to discuss with you. According to the current parsing rules, http_requests_time > avg(rate(http_requests_time[5m])) will be parsed as vector > vector, but in reality, it should be vector > scalar.

At present, only LiteralExpr(number) is regarded as a scalar. I think more judgment conditions may be needed to determine whether something is a scalar, such as whether there are labels in the metric query results.

Hi, @MasamiYui thank you for your review. There are a few points regarding your question.

  1. As long as your query is of type "instant vector" (even if there is only one value), the result will be a vector. You can verify this in the Prometheus UI and HTTP API.
  2. Aggregate functions such as avg, which you mentioned in your example, are not scalars in the true sense of the word. I consider them to be unlabeled scalars, and I believe that the expression you provided will not return a result.
  3. The normal approach would be to use the scalar function to return the sample value as a scalar, but the current PromqlQueryExecutor does not have the ability to parse resultType. I am currently working on refactoring this.
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Development

Successfully merging this pull request may close these issues.

5 participants