Repository: apache/skywalking (language: python)

Issue:
[Feature] Support rate, increase and abs functions in MQE(query and alerting)
### Search before asking

- [X] I had searched in the [issues](https://github.com/apache/skywalking/issues?q=is%3Aissue) and found no similar feature requirement.


### Description

These three functions could help the users to  identify the existence of the peak during the given time range.

Rate is for cases like values od traffic, suddenly much more or less traffic could be the beginning of chaos.
Compare to rate, increase(negative or positive) is for a certain value.
Abs is to make expression easier, and avoid greater or less than at both sides


### Use case

The context has been discussed https://github.com/apache/skywalking/discussions/11483

### Related issues

_No response_

### Are you willing to submit a pull request to implement this on your own?

- [ ] Yes I am willing to submit a pull request on my own!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct)



Discussion / hints:

Fix patch:
diff --git a/docs/en/api/metrics-query-expression.md b/docs/en/api/metrics-query-expression.md
index 6c5798c3fa62..2ca901e2eb31 100644
--- a/docs/en/api/metrics-query-expression.md
+++ b/docs/en/api/metrics-query-expression.md
@@ -296,6 +296,46 @@ view_as_seq(not_existing, service_cpm)
 #### Result Type
 The result type is determined by the type of selected not-null metric expression.
 
+## Trend Operation
+Trend Operation takes an expression and performs a trend calculation on its results.
+
+Expression:
+```text
+<Trend-Operator>(Metrics Expression, time_range)
+```
+
+`time_range` is the positive int of the calculated range. The unit will automatically align with to the query [Step](../../../oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/enumeration/Step.java),
+for example, if the query Step is `MINUTE`, the unit of `time_range` is `minute`.
+
+
+| Operator | Definition                                                                            | ExpressionResultType          |
+|----------|---------------------------------------------------------------------------------------|-------------------------------|
+| increase | returns the increase in the time range in the time series                             | TIME_SERIES_VALUES |
+| rate     | returns the per-second average rate of increase in the time range in the time series  | TIME_SERIES_VALUES |
+
+For example:
+If we want to query the increase value of the `service_cpm` metric in 2 minute(assume the query Step is MINUTE),
+we can use the following expression:
+
+```text
+increase(service_cpm, 2)
+```
+
+If the query duration is 3 minutes, from (T1 to T3) and the metric has values in time series:
+```text
+V(T1-2), V(T1-1), V(T1), V(T2), V(T3)
+```
+then the expression result is:
+```text
+V(T1)-V(T1-2), V(T2)-V(T1-1), V(T3)-V(T1)
+```
+
+**Note**:
+* If the calculated metric value is empty, the result will be empty. Assume in the T3 point, the increase value = V(T3)-V(T1), If the metric V(T3) or V(T1) is empty, the result value in T3 will be empty.
+
+### Result Type
+TIME_SERIES_VALUES.
+
 ## Expression Query Example
 ### Labeled Value Metrics
 ```text
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index b033310a1986..97f0ebc485bd 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -48,6 +48,7 @@
 * Replace Metrics v2 protocol with MQE in UI templates and E2E Test.
 * Fix incorrect apisix metrics otel rules.
 * Support `Scratch The OAP Config Dump`.
+* Support `increase/rate` function in the `MQE` query language.
 * Group service endpoints into `_abandoned` when endpoints have high
   cardinality.
 
diff --git a/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4 b/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
index b7a870a6bc12..69687a8702c9 100644
--- a/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
+++ b/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
@@ -59,6 +59,8 @@ ABS:         'abs';
 CEIL:        'ceil';
 FLOOR:       'floor';
 ROUND:       'round';
+INCREASE:    'increase';
+RATE:        'rate';
 
 // TopN
 TOP_N:        'top_n';
diff --git a/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4 b/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
index 0c2d9a931775..e67414422daa 100644
--- a/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
+++ b/oap-server/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
@@ -31,8 +31,9 @@ expression
     | aggregation L_PAREN expression R_PAREN # aggregationOp
     | mathematical_operator0 L_PAREN expression R_PAREN #mathematicalOperator0OP
     | mathematical_operator1 L_PAREN expression COMMA parameter R_PAREN #mathematicalOperator1OP
+    | trend L_PAREN metric COMMA INTEGER R_PAREN #trendOP
     | logical_operator L_PAREN expressionList R_PAREN #logicalOperatorOP
-    | topN L_PAREN metric COMMA parameter COMMA order R_PAREN  #topNOP
+    | topN L_PAREN metric COMMA INTEGER COMMA order R_PAREN  #topNOP
     | relabels L_PAREN expression COMMA label R_PAREN #relablesOP
     | aggregateLabels L_PAREN expression COMMA aggregateLabelsFunc R_PAREN #aggregateLabelsOp
     ;
@@ -69,6 +70,9 @@ mathematical_operator0:
 mathematical_operator1:
     ROUND;
 
+trend:
+    INCREASE | RATE;
+
 topN: TOP_N;
 
 logical_operator:
diff --git a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/MQEVisitorBase.java b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/MQEVisitorBase.java
index 2f272dc6c015..235abb30c88b 100644
--- a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/MQEVisitorBase.java
+++ b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/MQEVisitorBase.java
@@ -30,17 +30,24 @@
 import org.apache.skywalking.mqe.rt.operation.CompareOp;
 import org.apache.skywalking.mqe.rt.operation.LogicalFunctionOp;
 import org.apache.skywalking.mqe.rt.operation.MathematicalFunctionOp;
+import org.apache.skywalking.mqe.rt.operation.TrendOp;
 import org.apache.skywalking.mqe.rt.type.ExpressionResult;
 import org.apache.skywalking.mqe.rt.exception.IllegalExpressionException;
 import org.apache.skywalking.mqe.rt.type.ExpressionResultType;
 import org.apache.skywalking.mqe.rt.type.MQEValue;
 import org.apache.skywalking.mqe.rt.type.MQEValues;
 import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
 @Slf4j
 public abstract class MQEVisitorBase extends MQEParserBaseVisitor<ExpressionResult> {
     public final static String GENERAL_LABEL_NAME = "_";
+    public final Step queryStep;
+
+    protected MQEVisitorBase(final Step queryStep) {
+        this.queryStep = queryStep;
+    }
 
     @Override
     public ExpressionResult visitParensOp(MQEParser.ParensOpContext ctx) {
@@ -250,6 +257,34 @@ public ExpressionResult visitCompareOp(MQEParser.CompareOpContext ctx) {
         }
     }
 
+    @Override
+    public ExpressionResult visitTrendOP(MQEParser.TrendOPContext ctx) {
+        int opType = ctx.trend().getStart().getType();
+        int trendRange = Integer.parseInt(ctx.INTEGER().getText());
+        if (trendRange < 1) {
+            ExpressionResult result = new ExpressionResult();
+            result.setType(ExpressionResultType.UNKNOWN);
+            result.setError("The trend range must be greater than 0.");
+            return result;
+        }
+        ExpressionResult expResult = visit(ctx.metric());
+        if (StringUtil.isNotEmpty(expResult.getError())) {
+            return expResult;
+        }
+        if (expResult.getType() != ExpressionResultType.TIME_SERIES_VALUES) {
+            expResult.setError("The result of expression [" + ctx.metric().getText() + "] is not a time series result.");
+            return expResult;
+        }
+        try {
+            return TrendOp.doTrendOp(expResult, opType, trendRange, queryStep);
+        } catch (IllegalExpressionException e) {
+            ExpressionResult result = new ExpressionResult();
+            result.setType(ExpressionResultType.UNKNOWN);
+            result.setError(e.getMessage());
+            return result;
+        }
+    }
+
     @Override
     public abstract ExpressionResult visitMetric(MQEParser.MetricContext ctx);
 }
diff --git a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TrendOp.java b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TrendOp.java
new file mode 100644
index 000000000000..60c0887be363
--- /dev/null
+++ b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TrendOp.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.mqe.rt.operation;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.skywalking.mqe.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import org.apache.skywalking.mqe.rt.type.ExpressionResult;
+import org.apache.skywalking.mqe.rt.type.MQEValue;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
+
+public class TrendOp {
+    public static ExpressionResult doTrendOp(ExpressionResult expResult,
+                                             int opType,
+                                             int trendRange,
+                                             Step step) throws IllegalExpressionException {
+        switch (opType) {
+            case MQEParser.INCREASE:
+                return TrendOp.calculateIncrease(expResult, trendRange);
+            case MQEParser.RATE:
+                return TrendOp.calculateRate(expResult, trendRange, step);
+        }
+
+        throw new IllegalExpressionException("Unsupported function.");
+    }
+
+    private static ExpressionResult calculateIncrease(ExpressionResult expResult, int trendRange) {
+        expResult.getResults().forEach(resultValues -> {
+            List<MQEValue> mqeValues = resultValues.getValues();
+            List<MQEValue> newMqeValues = new ArrayList<>();
+            for (int i = trendRange; i < mqeValues.size(); i++) {
+                MQEValue mqeValue = mqeValues.get(i);
+                //if the current value is empty, then the trend value is empty
+                if (mqeValue.isEmptyValue()) {
+                    newMqeValues.add(mqeValue);
+                    continue;
+                }
+                MQEValue newMqeValue = new MQEValue();
+                newMqeValue.setId(mqeValue.getId());
+
+                //if the previous value is empty, then the trend value is empty
+                if (mqeValues.get(i - trendRange).isEmptyValue()) {
+                    newMqeValue.setEmptyValue(true);
+                    newMqeValues.add(newMqeValue);
+                    continue;
+                }
+
+                newMqeValue.setEmptyValue(mqeValue.isEmptyValue());
+                newMqeValue.setId(mqeValue.getId());
+                newMqeValue.setTraceID(mqeValue.getTraceID());
+                double newValue = mqeValue.getDoubleValue() - mqeValues.get(i - trendRange).getDoubleValue();
+                newMqeValue.setDoubleValue(newValue);
+                newMqeValues.add(newMqeValue);
+            }
+            resultValues.setValues(newMqeValues);
+        });
+        return expResult;
+    }
+
+    private static ExpressionResult calculateRate(ExpressionResult expResult, int trendRange, Step step) {
+        ExpressionResult result = calculateIncrease(expResult, trendRange);
+        long rangeSeconds;
+        switch (step) {
+            case SECOND:
+                rangeSeconds = trendRange;
+                break;
+            case MINUTE:
+                rangeSeconds = trendRange * 60;
+                break;
+            case HOUR:
+                rangeSeconds = trendRange * 3600;
+                break;
+            case DAY:
+                rangeSeconds = trendRange * 86400;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported step: " + step);
+        }
+        result.getResults().forEach(resultValues -> {
+            resultValues.getValues().forEach(mqeValue -> {
+                if (!mqeValue.isEmptyValue()) {
+                    double newValue = mqeValue.getDoubleValue() / rangeSeconds;
+                    mqeValue.setDoubleValue(newValue);
+                }
+            });
+        });
+        return result;
+    }
+}
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
index 76c6e6cc1abe..e5238719b7a3 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
@@ -58,6 +58,7 @@ public class AlarmRule {
     private String message;
     private Map<String, String> tags;
     private Set<String> hooks;
+    private int maxTrendRange;
 
     /**
      * Init includeMetrics and verify the expression.
@@ -67,6 +68,7 @@ public void setExpression(final String expression) throws IllegalExpressionExcep
         MQELexer lexer = new MQELexer(CharStreams.fromString(expression));
         lexer.addErrorListener(new ParseErrorListener());
         MQEParser parser = new MQEParser(new CommonTokenStream(lexer));
+        parser.addErrorListener(new ParseErrorListener());
         ParseTree tree;
         try {
             tree = parser.expression();
@@ -88,6 +90,7 @@ public void setExpression(final String expression) throws IllegalExpressionExcep
         verifyIncludeMetrics(visitor.getIncludeMetrics(), expression);
         this.expression = expression;
         this.includeMetrics = visitor.getIncludeMetrics();
+        this.maxTrendRange = visitor.getMaxTrendRange();
     }
 
     private void verifyIncludeMetrics(Set<String> includeMetrics, String expression) throws IllegalExpressionException {
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
index 961473bb0d15..a42e73f5604a 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
@@ -82,6 +82,8 @@ public class RunningRule {
     private final Set<String> hooks;
     private final Set<String> includeMetrics;
     private final ParseTree exprTree;
+    // The additional period is used to calculate the trend.
+    private final int additionalPeriod;
 
     public RunningRule(AlarmRule alarmRule) {
         expression = alarmRule.getExpression();
@@ -108,6 +110,7 @@ public RunningRule(AlarmRule alarmRule) {
         MQEParser parser = new MQEParser(new CommonTokenStream(lexer));
         parser.addErrorListener(new ParseErrorListener());
         this.exprTree = parser.expression();
+        this.additionalPeriod = alarmRule.getMaxTrendRange();
     }
 
     /**
@@ -134,7 +137,7 @@ public void in(MetaInAlarm meta, Metrics metrics) {
         AlarmEntity entity = new AlarmEntity(
             meta.getScope(), meta.getScopeId(), meta.getName(), meta.getId0(), meta.getId1());
 
-        Window window = windows.computeIfAbsent(entity, ignored -> new Window(period));
+        Window window = windows.computeIfAbsent(entity, ignored -> new Window(this.period, this.additionalPeriod));
         window.add(meta.getMetricsName(), metrics);
     }
 
@@ -234,13 +237,15 @@ public List<AlarmMessage> check() {
      */
     public class Window {
         private LocalDateTime endTime;
-        private final int period;
+        private final int additionalPeriod;
+        private final int size;
         private int silenceCountdown;
         private LinkedList<Map<String, Metrics>> values;
         private ReentrantLock lock = new ReentrantLock();
 
-        public Window(int period) {
-            this.period = period;
+        public Window(int period, int additionalPeriod) {
+            this.additionalPeriod = additionalPeriod;
+            this.size = period + additionalPeriod;
             // -1 means silence countdown is not running.
             silenceCountdown = -1;
             init();
@@ -342,7 +347,7 @@ public Optional<AlarmMessage> checkAlarm() {
 
         private boolean isMatch() {
             int isMatch = 0;
-            AlarmMQEVisitor visitor = new AlarmMQEVisitor(this.values, this.endTime);
+            AlarmMQEVisitor visitor = new AlarmMQEVisitor(this.values, this.endTime, this.additionalPeriod);
             ExpressionResult parseResult = visitor.visit(exprTree);
             if (StringUtil.isNotBlank(parseResult.getError())) {
                 log.error("expression:" + expression + " error: " + parseResult.getError());
@@ -403,7 +408,7 @@ public boolean isExpired() {
 
         private void init() {
             values = new LinkedList<>();
-            for (int i = 0; i < period; i++) {
+            for (int i = 0; i < size; i++) {
                 values.add(null);
             }
         }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVerifyVisitor.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVerifyVisitor.java
index c6c556f9c87e..cc0b861339a1 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVerifyVisitor.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVerifyVisitor.java
@@ -35,6 +35,7 @@
 import org.apache.skywalking.mqe.rt.type.MQEValues;
 import org.apache.skywalking.mqe.rt.type.Metadata;
 import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
 import org.apache.skywalking.oap.server.core.query.type.KeyValue;
 import org.apache.skywalking.oap.server.core.storage.annotation.Column;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
@@ -48,6 +49,11 @@
 @Slf4j
 public class AlarmMQEVerifyVisitor extends MQEVisitorBase {
     private final Set<String> includeMetrics = new HashSet<>();
+    private int maxTrendRange = 0;
+
+    public AlarmMQEVerifyVisitor() {
+        super(Step.MINUTE);
+    }
 
     @Override
     public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
@@ -113,4 +119,23 @@ public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
             return result;
         }
     }
+
+    @Override
+    public ExpressionResult visitTrendOP(MQEParser.TrendOPContext ctx) {
+        int trendRange = Integer.parseInt(ctx.INTEGER().getText());
+        if (trendRange < 1) {
+            ExpressionResult result = new ExpressionResult();
+            result.setType(ExpressionResultType.UNKNOWN);
+            result.setError("The trend range must be greater than 0.");
+            return result;
+        }
+        setMaxTrendRange(trendRange);
+        return super.visitTrendOP(ctx);
+    }
+
+    private void setMaxTrendRange(int trendRange) {
+        if (trendRange > maxTrendRange) {
+            maxTrendRange = trendRange;
+        }
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVisitor.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVisitor.java
index 98a32d791bcc..712ff70e9a32 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVisitor.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/expr/rt/AlarmMQEVisitor.java
@@ -44,6 +44,7 @@
 import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
 import org.apache.skywalking.mqe.rt.MQEVisitorBase;
 import org.apache.skywalking.oap.server.core.analysis.metrics.MultiIntValuesHolder;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
 import org.apache.skywalking.oap.server.core.query.type.KeyValue;
 import org.apache.skywalking.oap.server.core.storage.annotation.Column;
 import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
@@ -59,15 +60,19 @@ public class AlarmMQEVisitor extends MQEVisitorBase {
     private final int windowSize;
     private final LocalDateTime endTime;
     private final ArrayList<String> windowTimes;
+    private final int maxTrendRange;
 
     public AlarmMQEVisitor(final LinkedList<Map<String, Metrics>> metricsValues,
-                           final LocalDateTime endTime) {
+                           final LocalDateTime endTime,
+                           final int maxTrendRange) {
+        super(Step.MINUTE);
         this.metricsValues = metricsValues;
         this.commonValuesMap = new HashMap<>();
         this.labeledValuesMap = new HashMap<>();
         this.endTime = endTime;
         this.windowSize = metricsValues.size();
         this.windowTimes = initWindowTimes();
+        this.maxTrendRange = maxTrendRange;
         this.initMetricsValues();
     }
 
@@ -114,12 +119,38 @@ public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
             result.setError("Unsupported value type: " + dataType);
             return result;
         }
-
+        if (!(ctx.parent instanceof MQEParser.TrendOPContext)) {
+            //Trim the redundant data
+            result.getResults().forEach(resultValues -> {
+                List<MQEValue> mqeValues = resultValues.getValues();
+                if (maxTrendRange > 0 && mqeValues.size() > maxTrendRange) {
+                    resultValues.setValues(mqeValues.subList(maxTrendRange, mqeValues.size()));
+                }
+            });
+        }
         result.setResults(mqeValuesList);
         result.setType(ExpressionResultType.TIME_SERIES_VALUES);
         return result;
     }
 
+    @Override
+    public ExpressionResult visitTrendOP(MQEParser.TrendOPContext ctx) {
+        ExpressionResult result = super.visitTrendOP(ctx);
+        int trendRange = Integer.parseInt(ctx.INTEGER().getText());
+        //super.visitTrendOP only trim self trend range, trim more here due to all metrics window size is the same
+        int trimIndex = maxTrendRange - trendRange;
+        if (trimIndex > 0) {
+            //Trim the redundant data
+            result.getResults().forEach(resultValues -> {
+                List<MQEValue> mqeValues = resultValues.getValues();
+                if (mqeValues.size() > trimIndex) {
+                    resultValues.setValues(mqeValues.subList(trimIndex, mqeValues.size()));
+                }
+            });
+        }
+        return result;
+    }
+
     private ArrayList<String> initWindowTimes() {
         ArrayList<String> windowTimes = new ArrayList<>();
         for (int i = this.windowSize - 1; i >= 0; i--) {
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
index e3195eb31e40..519f142e00ac 100644
--- a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
@@ -29,7 +29,7 @@
 import com.linecorp.armeria.server.annotation.Param;
 import com.linecorp.armeria.server.annotation.Path;
 import com.linecorp.armeria.server.annotation.Post;
-import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java
index e54481c45876..769cb0735dad 100644
--- a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java
@@ -18,7 +18,7 @@
 
 package org.apache.skywalking.oap.query.promql.rt.exception;
 
-import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.antlr.v4.runtime.BaseErrorListener;
 import org.antlr.v4.runtime.RecognitionException;
 import org.antlr.v4.runtime.Recognizer;
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java
index 9f112429f62e..9b36208bc36f 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java
@@ -52,6 +52,7 @@
 import org.apache.skywalking.mqe.rt.MQEVisitorBase;
 import org.apache.skywalking.mqe.rt.type.ExpressionResult;
 import org.apache.skywalking.mqe.rt.type.ExpressionResultType;
+import org.joda.time.DateTime;
 
 @Slf4j
 public class MQEVisitor extends MQEVisitorBase {
@@ -64,6 +65,7 @@ public MQEVisitor(final MetricsQuery metricsQuery,
                       final RecordsQuery recordsQuery,
                       final Entity entity,
                       final Duration duration) {
+        super(duration.getStep());
         this.metricsQuery = metricsQuery;
         this.recordsQuery = recordsQuery;
         this.entity = entity;
@@ -87,14 +89,27 @@ public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
             if (Column.ValueDataType.COMMON_VALUE == dataType) {
                 if (ctx.parent instanceof MQEParser.TopNOPContext) {
                     MQEParser.TopNOPContext parent = (MQEParser.TopNOPContext) ctx.parent;
-                    querySortMetrics(metricName, Integer.parseInt(parent.parameter().getText()),
+                    int topN = Integer.parseInt(parent.INTEGER().getText());
+                    if (topN <= 0) {
+                        throw new IllegalExpressionException("TopN value must be > 0.");
+                    }
+                    querySortMetrics(metricName, Integer.parseInt(parent.INTEGER().getText()),
                                      Order.valueOf(parent.order().getText().toUpperCase()), result
                     );
+                } else if (ctx.parent instanceof MQEParser.TrendOPContext) {
+                    //trend query requires get previous data according to the trend range
+                    MQEParser.TrendOPContext parent = (MQEParser.TrendOPContext) ctx.parent;
+                    int trendRange = Integer.parseInt(parent.INTEGER().getText());
+                    queryMetrics(metricName, getTrendQueryDuration(trendRange), result);
                 } else {
-                    queryMetrics(metricName, result);
+                    queryMetrics(metricName, this.duration, result);
                 }
             } else if (Column.ValueDataType.LABELED_VALUE == dataType) {
                 List<String> queryLabelList = Collections.emptyList();
+                if (ctx.parent instanceof MQEParser.TopNOPContext) {
+                    throw new IllegalExpressionException(
+                        "Metric: [" + metricName + "] is labeled value, dose not support top_n query.");
+                }
                 if (ctx.label() != null) {
                     String labelValue = ctx.label().labelValue().getText();
                     String labelValueTrim = labelValue.substring(1, labelValue.length() - 1);
@@ -102,11 +117,21 @@ public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
                         queryLabelList = Arrays.asList(labelValueTrim.split(Const.COMMA));
                     }
                 }
-                queryLabeledMetrics(metricName, queryLabelList, result);
+                if (ctx.parent instanceof MQEParser.TrendOPContext) {
+                    MQEParser.TrendOPContext parent = (MQEParser.TrendOPContext) ctx.parent;
+                    int trendRange = Integer.parseInt(parent.INTEGER().getText());
+                    queryLabeledMetrics(metricName, queryLabelList, getTrendQueryDuration(trendRange), result);
+                } else {
+                    queryLabeledMetrics(metricName, queryLabelList, this.duration, result);
+                }
             } else if (Column.ValueDataType.SAMPLED_RECORD == dataType) {
                 if (ctx.parent instanceof MQEParser.TopNOPContext) {
                     MQEParser.TopNOPContext parent = (MQEParser.TopNOPContext) ctx.parent;
-                    queryRecords(metricName, Integer.parseInt(parent.parameter().getText()),
+                    int topN = Integer.parseInt(parent.INTEGER().getText());
+                    if (topN <= 0) {
+                        throw new IllegalExpressionException("TopN value must be > 0.");
+                    }
+                    queryRecords(metricName, Integer.parseInt(parent.INTEGER().getText()),
                                  Order.valueOf(parent.order().getText().toUpperCase()), result
                     );
                 } else {
@@ -182,15 +207,15 @@ private void queryRecords(String metricName, int topN, Order order, ExpressionRe
         result.setType(ExpressionResultType.RECORD_LIST);
     }
 
-    private void queryMetrics(String metricName, ExpressionResult result) throws IOException {
+    private void queryMetrics(String metricName, Duration queryDuration, ExpressionResult result) throws IOException {
         MetricsCondition metricsCondition = new MetricsCondition();
         metricsCondition.setName(metricName);
         metricsCondition.setEntity(entity);
-        MetricsValues metricsValues = metricsQuery.readMetricsValues(metricsCondition, duration);
-        List<PointOfTime> times = duration.assembleDurationPoints();
+        MetricsValues metricsValues = metricsQuery.readMetricsValues(metricsCondition, queryDuration);
+        List<PointOfTime> times = queryDuration.assembleDurationPoints();
         List<MQEValue> mqeValueList = new ArrayList<>(times.size());
         for (int i = 0; i < times.size(); i++) {
-            long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(duration.getStep(), times.get(i).getPoint())
+            long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(queryDuration.getStep(), times.get(i).getPoint())
                                                       .getMillis();
             KVInt kvInt = metricsValues.getValues().getValues().get(i);
             MQEValue mqeValue = new MQEValue();
@@ -209,17 +234,18 @@ private void queryMetrics(String metricName, ExpressionResult result) throws IOE
 
     private void queryLabeledMetrics(String metricName,
                                      List<String> queryLabelList,
+                                     Duration queryDuration,
                                      ExpressionResult result) throws IOException {
         MetricsCondition metricsCondition = new MetricsCondition();
         metricsCondition.setName(metricName);
         metricsCondition.setEntity(entity);
         List<MetricsValues> metricsValuesList = metricsQuery.readLabeledMetricsValues(
-            metricsCondition, queryLabelList, duration);
-        List<PointOfTime> times = duration.assembleDurationPoints();
+            metricsCondition, queryLabelList, queryDuration);
+        List<PointOfTime> times = queryDuration.assembleDurationPoints();
         metricsValuesList.forEach(metricsValues -> {
             List<MQEValue> mqeValueList = new ArrayList<>(times.size());
             for (int i = 0; i < times.size(); i++) {
-                long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(duration.getStep(), times.get(i).getPoint())
+                long retTimestamp = DurationUtils.INSTANCE.parseToDateTime(queryDuration.getStep(), times.get(i).getPoint())
                                                           .getMillis();
                 KVInt kvInt = metricsValues.getValues().getValues().get(i);
                 MQEValue mqeValue = new MQEValue();
@@ -242,4 +268,29 @@ private void queryLabeledMetrics(String metricName,
         result.setType(ExpressionResultType.TIME_SERIES_VALUES);
         result.setLabeledResult(true);
     }
+
+    private Duration getTrendQueryDuration(int stepRange) {
+        Duration duration = new Duration();
+        duration.setStep(this.duration.getStep());
+        duration.setEnd(this.duration.getEnd());
+        DateTime startDT = new DateTime(this.duration.getStartTimestamp());
+
+        switch (duration.getStep()) {
+            case DAY:
+                duration.setStart(startDT.minusDays(stepRange).toString(DurationUtils.YYYY_MM_DD));
+                break;
+            case HOUR:
+                duration.setStart(startDT.minusHours(stepRange).toString(DurationUtils.YYYY_MM_DD_HH));
+                break;
+            case MINUTE:
+                duration.setStart(startDT.minusMinutes(stepRange).toString(DurationUtils.YYYY_MM_DD_HHMM));
+                break;
+            case SECOND:
+                duration.setStart(startDT.minusSeconds(stepRange).toString(DurationUtils.YYYY_MM_DD_HHMMSS));
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported query step: " + duration.getStep());
+        }
+        return duration;
+    }
 }
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java
index e10c71465e3b..89cb999dfea2 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java
@@ -19,7 +19,7 @@
 package org.apache.skywalking.oap.query.graphql.resolver;
 
 import graphql.kickstart.tools.GraphQLQueryResolver;
-import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.misc.ParseCancellationException;
 import java.text.DecimalFormat;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;


Test patch:
diff --git a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TrendOpTest.java b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TrendOpTest.java
new file mode 100644
index 000000000000..3c5514123837
--- /dev/null
+++ b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TrendOpTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.mqe.rt;
+
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import org.apache.skywalking.mqe.rt.operation.TrendOp;
+import org.apache.skywalking.mqe.rt.type.ExpressionResult;
+import org.apache.skywalking.mqe.rt.type.ExpressionResultType;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TrendOpTest {
+    private final MockData mockData = new MockData();
+
+    @Test
+    public void increaseNoLabeledTest() throws Exception {
+        ExpressionResult increase = TrendOp.doTrendOp(
+            mockData.newSeriesNoLabeledResult(100, 280), MQEParser.INCREASE, 1, Step.MINUTE);
+        assertEquals(ExpressionResultType.TIME_SERIES_VALUES, increase.getType());
+        assertEquals("300", increase.getResults().get(0).getValues().get(0).getId());
+        assertEquals(180, increase.getResults().get(0).getValues().get(0).getDoubleValue());
+    }
+
+    @Test
+    public void increaseLabeledTest() throws Exception {
+        ExpressionResult increase = TrendOp.doTrendOp(
+            mockData.newSeriesLabeledResult(100, 280, 100, 220), MQEParser.INCREASE, 1, Step.MINUTE);
+        assertEquals(ExpressionResultType.TIME_SERIES_VALUES, increase.getType());
+        assertEquals("300", increase.getResults().get(0).getValues().get(0).getId());
+        assertEquals(180, increase.getResults().get(0).getValues().get(0).getDoubleValue());
+        assertEquals("300", increase.getResults().get(1).getValues().get(0).getId());
+        assertEquals(120, increase.getResults().get(1).getValues().get(0).getDoubleValue());
+    }
+
+    @Test
+    public void rateNoLabeledTest() throws Exception {
+        ExpressionResult rate = TrendOp.doTrendOp(
+            mockData.newSeriesNoLabeledResult(100, 280), MQEParser.RATE, 1, Step.MINUTE);
+        assertEquals(ExpressionResultType.TIME_SERIES_VALUES, rate.getType());
+        assertEquals("300", rate.getResults().get(0).getValues().get(0).getId());
+        assertEquals(3, rate.getResults().get(0).getValues().get(0).getDoubleValue());
+    }
+
+    @Test
+    public void rateLabeledTest() throws Exception {
+        ExpressionResult rate = TrendOp.doTrendOp(
+            mockData.newSeriesLabeledResult(100, 280, 100, 220), MQEParser.RATE, 1, Step.MINUTE);
+        assertEquals(ExpressionResultType.TIME_SERIES_VALUES, rate.getType());
+        assertEquals("300", rate.getResults().get(0).getValues().get(0).getId());
+        assertEquals(3, rate.getResults().get(0).getValues().get(0).getDoubleValue());
+        assertEquals("300", rate.getResults().get(1).getValues().get(0).getId());
+        assertEquals(2, rate.getResults().get(1).getValues().get(0).getDoubleValue());
+    }
+}
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRuleTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRuleTest.java
index 64e353587ac1..5cf7cbcf18d9 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRuleTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRuleTest.java
@@ -113,5 +113,8 @@ public void testExpressionVerify() throws IllegalExpressionException {
             rule.setExpression("sum(service_percent > endpoint_percent) >= 1");
         }).getMessage().contains("The metrics in expression: sum(service_percent > endpoint_percent) >= 1 must have the same scope level, but got:"));
 
+        //trend expression
+        rule.setExpression("sum((increase(service_percent,5) + increase(service_percent,2)) > 0) >= 1");
+        Assertions.assertEquals(5, rule.getMaxTrendRange());
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
index 60d2932fbabd..a0f66fc04130 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
@@ -71,10 +71,11 @@ public void setup() {
     @Test
     public void testInitAndStart() throws IllegalExpressionException {
         AlarmRule alarmRule = new AlarmRule();
-        alarmRule.setAlarmRuleName("endpoint_percent_rule");
-        alarmRule.setExpression("sum(endpoint_percent < 75) >= 3");
+        alarmRule.setAlarmRuleName("mix_rule");
+        alarmRule.setExpression("sum((increase(endpoint_cpm,5) + increase(endpoint_percent,2)) > 0) >= 1");
         alarmRule.getIncludeMetrics().add("endpoint_percent");
-        alarmRule.setPeriod(15);
+        alarmRule.getIncludeMetrics().add("endpoint_cpm");
+        alarmRule.setPeriod(10);
         alarmRule.setTags(new HashMap<String, String>() {{
             put("key", "value");
         }});
@@ -90,11 +91,11 @@ public void testInitAndStart() throws IllegalExpressionException {
 
         RunningRule.Window window = windows.get(getAlarmEntity(123));
         LocalDateTime endTime = Whitebox.getInternalState(window, "endTime");
-        int period = Whitebox.getInternalState(window, "period");
+        int additionalPeriod = Whitebox.getInternalState(window, "additionalPeriod");
         LinkedList<Metrics> metricsBuffer = Whitebox.getInternalState(window, "values");
 
         Assertions.assertTrue(targetTime.equals(endTime.toDateTime()));
-        Assertions.assertEquals(15, period);
+        Assertions.assertEquals(5, additionalPeriod);
         Assertions.assertEquals(15, metricsBuffer.size());
     }
 
diff --git a/test/e2e-v2/cases/alarm/alarm-settings.yml b/test/e2e-v2/cases/alarm/alarm-settings.yml
index 40df6f0a75d8..abcc8c130aa9 100755
--- a/test/e2e-v2/cases/alarm/alarm-settings.yml
+++ b/test/e2e-v2/cases/alarm/alarm-settings.yml
@@ -16,10 +16,10 @@
 rules:
   # service response time > 10ms
   service_resp_time_rule:
-    expression: sum(service_resp_time > 10) >= 1
+    expression: sum(abs(increase(service_resp_time,1)) > 0) >= 1
     period: 10
     silence-period: 1
-    message: Response time of service {name} is more than 10ms in 1 minutes of last 10 minutes.
+    message: Response time of service {name} is increase/decrease in 1 minutes of last 10 minutes.
     tags:
       level: WARNING
       receivers: lisi
diff --git a/test/e2e-v2/cases/alarm/es/e2e.yaml b/test/e2e-v2/cases/alarm/es/e2e.yaml
index e0ad564928fc..4dcb90bf0f7d 100644
--- a/test/e2e-v2/cases/alarm/es/e2e.yaml
+++ b/test/e2e-v2/cases/alarm/es/e2e.yaml
@@ -31,7 +31,7 @@ setup:
 trigger:
   action: http
   interval: 3s
-  times: 10
+  times: 30
   url: http://${provider_host}:${provider_9090}/users
   method: POST
   body: '{"id":"123","name":"skywalking"}'
diff --git a/test/e2e-v2/cases/alarm/es/es-sharding/e2e.yaml b/test/e2e-v2/cases/alarm/es/es-sharding/e2e.yaml
index 24e01c081230..a1fa0e36b91d 100644
--- a/test/e2e-v2/cases/alarm/es/es-sharding/e2e.yaml
+++ b/test/e2e-v2/cases/alarm/es/es-sharding/e2e.yaml
@@ -31,7 +31,7 @@ setup:
 trigger:
   action: http
   interval: 3s
-  times: 10
+  times: 30
   url: http://${provider_host}:${provider_9090}/users
   method: POST
   body: '{"id":"123","name":"skywalking"}'
diff --git a/test/e2e-v2/cases/alarm/expected/silence-after-graphql-warn.yml b/test/e2e-v2/cases/alarm/expected/silence-after-graphql-warn.yml
index 641150f9dac3..c3f71b81ce9c 100644
--- a/test/e2e-v2/cases/alarm/expected/silence-after-graphql-warn.yml
+++ b/test/e2e-v2/cases/alarm/expected/silence-after-graphql-warn.yml
@@ -42,7 +42,7 @@ msgs:
   - starttime: {{ gt .starttime 0 }}
     scope: Service
     id: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
-    message: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    message: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     tags:
       - key: level
         value: WARNING
@@ -90,7 +90,7 @@ msgs:
   - starttime: {{ gt .starttime 0 }}
     scope: Service
     id: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
-    message: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    message: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     tags:
       - key: level
         value: WARNING
diff --git a/test/e2e-v2/cases/alarm/expected/silence-after-webhook.yml b/test/e2e-v2/cases/alarm/expected/silence-after-webhook.yml
index f1d0f0355dfe..3e9ad6d25bac 100644
--- a/test/e2e-v2/cases/alarm/expected/silence-after-webhook.yml
+++ b/test/e2e-v2/cases/alarm/expected/silence-after-webhook.yml
@@ -21,7 +21,7 @@ messages:
     id0: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
     id1: ""
     ruleName: service_resp_time_rule
-    alarmMessage: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    alarmMessage: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     startTime: {{ gt .startTime 0 }}
     tags:
       - key: level
@@ -47,7 +47,7 @@ messages:
     id0: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
     id1: ""
     ruleName: service_resp_time_rule
-    alarmMessage: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    alarmMessage: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     startTime: {{ gt .startTime 0 }}
     tags:
       - key: level
diff --git a/test/e2e-v2/cases/alarm/expected/silence-before-graphql-warn.yml b/test/e2e-v2/cases/alarm/expected/silence-before-graphql-warn.yml
index e8d5c1ca882f..a766327f30c7 100644
--- a/test/e2e-v2/cases/alarm/expected/silence-before-graphql-warn.yml
+++ b/test/e2e-v2/cases/alarm/expected/silence-before-graphql-warn.yml
@@ -42,7 +42,7 @@ msgs:
   - starttime: {{ gt .starttime 0 }}
     scope: Service
     id: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
-    message: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    message: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     tags:
       - key: level
         value: WARNING
diff --git a/test/e2e-v2/cases/alarm/expected/silence-before-webhook.yml b/test/e2e-v2/cases/alarm/expected/silence-before-webhook.yml
index c076fd0c0bb1..d34f856f282f 100644
--- a/test/e2e-v2/cases/alarm/expected/silence-before-webhook.yml
+++ b/test/e2e-v2/cases/alarm/expected/silence-before-webhook.yml
@@ -21,7 +21,7 @@ messages:
     id0: ZTJlLXNlcnZpY2UtcHJvdmlkZXI=.1
     id1: ""
     ruleName: service_resp_time_rule
-    alarmMessage: Response time of service e2e-service-provider is more than 10ms in 1 minutes of last 10 minutes.
+    alarmMessage: Response time of service e2e-service-provider is increase/decrease in 1 minutes of last 10 minutes.
     startTime: {{ gt .startTime 0 }}
     tags:
       - key: level
diff --git a/test/e2e-v2/cases/alarm/h2/e2e.yaml b/test/e2e-v2/cases/alarm/h2/e2e.yaml
index e0ad564928fc..4dcb90bf0f7d 100644
--- a/test/e2e-v2/cases/alarm/h2/e2e.yaml
+++ b/test/e2e-v2/cases/alarm/h2/e2e.yaml
@@ -31,7 +31,7 @@ setup:
 trigger:
   action: http
   interval: 3s
-  times: 10
+  times: 30
   url: http://${provider_host}:${provider_9090}/users
   method: POST
   body: '{"id":"123","name":"skywalking"}'
diff --git a/test/e2e-v2/cases/alarm/mysql/e2e.yaml b/test/e2e-v2/cases/alarm/mysql/e2e.yaml
index e0ad564928fc..4dcb90bf0f7d 100644
--- a/test/e2e-v2/cases/alarm/mysql/e2e.yaml
+++ b/test/e2e-v2/cases/alarm/mysql/e2e.yaml
@@ -31,7 +31,7 @@ setup:
 trigger:
   action: http
   interval: 3s
-  times: 10
+  times: 30
   url: http://${provider_host}:${provider_9090}/users
   method: POST
   body: '{"id":"123","name":"skywalking"}'
diff --git a/test/e2e-v2/cases/alarm/postgres/e2e.yaml b/test/e2e-v2/cases/alarm/postgres/e2e.yaml
index e0ad564928fc..4dcb90bf0f7d 100644
--- a/test/e2e-v2/cases/alarm/postgres/e2e.yaml
+++ b/test/e2e-v2/cases/alarm/postgres/e2e.yaml
@@ -31,7 +31,7 @@ setup:
 trigger:
   action: http
   interval: 3s
-  times: 10
+  times: 30
   url: http://${provider_host}:${provider_9090}/users
   method: POST
   body: '{"id":"123","name":"skywalking"}'
diff --git a/test/e2e-v2/cases/mqe/expected/trend-OP.yml b/test/e2e-v2/cases/mqe/expected/trend-OP.yml
new file mode 100644
index 000000000000..a4474f13e816
--- /dev/null
+++ b/test/e2e-v2/cases/mqe/expected/trend-OP.yml
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+type: TIME_SERIES_VALUES
+results:
+{{- contains .results }}
+  - metric:
+      labels: []
+    values:
+      {{- contains .values }}
+      - id: {{ notEmpty .id }}
+        value: {{ .value }}
+        traceid: null
+      - id: {{ notEmpty .id }}
+        value: null
+        traceid: null
+      {{- end}}
+{{- end}}
+error: null
diff --git a/test/e2e-v2/cases/mqe/mqe-cases.yaml b/test/e2e-v2/cases/mqe/mqe-cases.yaml
index d857cfbf378c..c9cfe7af77d4 100644
--- a/test/e2e-v2/cases/mqe/mqe-cases.yaml
+++ b/test/e2e-v2/cases/mqe/mqe-cases.yaml
@@ -67,3 +67,9 @@ cases:
   # viewAsSeq-OP
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="view_as_seq(mq_service_consume_sla,service_sla)" --service-name=e2e-service-provider
     expected: expected/viewAsSeq-OP.yml
+
+  # trend-OP
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="increase(service_resp_time,2)" --service-name=e2e-service-provider
+    expected: expected/trend-OP.yml
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="rate(service_resp_time,2)" --service-name=e2e-service-provider
+    expected: expected/trend-OP.yml