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

wankai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 61b4674c16 Support aggregation operators in PromQL query. (#12431)
61b4674c16 is described below

commit 61b4674c16f392e1e8ee01101d4e68278fb6b0af
Author: weixiang1862 <652048...@qq.com>
AuthorDate: Fri Jul 12 16:29:03 2024 +0800

    Support aggregation operators in PromQL query. (#12431)
---
 docs/en/api/promql-service.md                      |  27 ++++
 docs/en/changes/changes.md                         |   1 +
 .../skywalking/promql/rt/grammar/PromQLLexer.g4    |   9 ++
 .../skywalking/promql/rt/grammar/PromQLParser.g4   |   9 ++
 .../oap/query/promql/entity/TimeValuePair.java     |   4 +-
 .../oap/query/promql/rt/PromOpUtils.java           |  78 ++++++++++
 .../query/promql/rt/PromQLExprQueryVisitor.java    |  48 +++++++
 .../rt/parser/PromQLExprQueryVisitorTest.java      | 159 +++++++++++++++++++++
 ...ervice-metric-labeled-matrix-aggregate-by-p.yml |  46 ++++++
 ...e-metric-labeled-matrix-aggregate-without-p.yml |  32 +++++
 test/e2e-v2/cases/promql/promql-cases.yaml         |   4 +
 11 files changed, 415 insertions(+), 2 deletions(-)

diff --git a/docs/en/api/promql-service.md b/docs/en/api/promql-service.md
index 8efb66e82f..88861ae03e 100644
--- a/docs/en/api/promql-service.md
+++ b/docs/en/api/promql-service.md
@@ -106,6 +106,33 @@ For example:
 service_cpm{service='service_A', layer='$layer'} > 
service_cpm{service='service_B', layer='$layer'}
 ```
 
+### Aggregation operators
+[Prometheus Docs 
Reference](https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators)
+
+| Operator | Definition                            | Support |
+|----------|---------------------------------------|---------|
+| sum      | calculate sum over dimensions         | yes     |
+| min      | select minimum over dimensions        | yes     |
+| max      | select maximum over dimensions        | yes     |
+| avg      | calculate the average over dimensions | yes     |
+
+For example:
+
+If the metric `http_requests_total` had time series that fan out by `service`, 
`service_instance_id`, and `group` labels,
+we could calculate the total number of seen HTTP requests per service and 
group over all service instances via:
+
+```
+sum by (service, group) (http_requests_total{service='$service', 
layer='$layer'})
+```
+Which is equivalent to:
+```
+sum without (service_instance_id) (http_requests_total{service='$service', 
layer='$layer'})
+```
+If we are just interested in the total of HTTP requests we have seen in all 
services, we could simply write:
+```
+sum(http_requests_total{service='$service', layer='$layer'})
+```
+
 ### HTTP API
 
 #### Expression queries
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index f41d34f202..d0b0736441 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -31,6 +31,7 @@
 * Fix expression of graph `Current QPS` in MySQL dashboard.
 * Support tracing logs query for debugging.
 * BanyanDB: fix Tag autocomplete data storage and query.
+* Support aggregation operators in PromQL query.
 
 #### UI
 * Highlight search log keywords.
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
 
b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
index c82dd1a0c8..5fc2b7c5da 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
@@ -45,6 +45,15 @@ LT:          '<';
 GTE:         '>=';
 GT:          '>';
 
+// Aggregation operators
+AVG:         'avg';
+MAX:         'max';
+MIN:         'min';
+SUM:         'sum';
+
+BY:          'by';
+WITHOUT:     'without';
+
 // Literals
 NUMBER: Digit+ (DOT Digit+)?;
 DURATION: Digit+ ('ms' | 's' | 'm' | 'h' | 'd' | 'w');
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
 
b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
index 847e23aabd..57b2681caa 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
@@ -27,6 +27,8 @@ expression
     | expression mulDivMod expression  # mulDivModOp
     | expression addSub expression     # addSubOp
     | expression compare expression    # compareOp
+    | aggregationFunc (aggregationClause)? L_PAREN expression R_PAREN   # 
aggregationOp
+    | aggregationFunc L_PAREN expression R_PAREN (aggregationClause)?   # 
aggregationOp
     ;
 
 expressionNode:  metricInstant| metricRange| numberLiteral| badRange;
@@ -35,6 +37,12 @@ addSub:          ADD | SUB ;
 mulDivMod:       MUL | DIV | MOD;
 compare:        (DEQ | NEQ | LTE | LT | GTE | GT) BOOL?;
 
+aggregationFunc:
+    AVG | SUM | MAX | MIN;
+
+aggregationClause:
+    (BY | WITHOUT) L_PAREN labelNameList R_PAREN;
+
 metricName:      NAME_STRING;
 metricInstant:   metricName | metricName L_BRACE labelList? R_BRACE;
 metricRange:     metricInstant L_BRACKET DURATION R_BRACKET;
@@ -43,6 +51,7 @@ labelName:       NAME_STRING;
 labelValue:      VALUE_STRING;
 label:           labelName EQ labelValue;
 labelList:       label (COMMA label)*;
+labelNameList:   labelName (COMMA labelName)*;
 
 numberLiteral:   NUMBER;
 
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java
 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java
index 68e32065c2..3d29522807 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java
@@ -25,8 +25,8 @@ import 
org.apache.skywalking.oap.query.promql.entity.codec.TimeValuePairSerializ
 @Data
 @JsonSerialize(using = TimeValuePairSerializer.class)
 public class TimeValuePair {
-    private final long time;
-    private final String value;
+    private long time;
+    private String value;
 
     public TimeValuePair(final long time, String value) {
         this.time = time;
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
index d439433ace..c29ba62e08 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
@@ -20,8 +20,18 @@ package org.apache.skywalking.oap.query.promql.rt;
 
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.AggregateLabelsFunc;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.AggregateLabelsFuncFactory;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.AvgAggregateLabelsFunc;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.MaxAggregateLabelsFunc;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.MinAggregateLabelsFunc;
+import 
org.apache.skywalking.mqe.rt.operation.aggregatelabels.SumAggregateLabelsFunc;
+import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
 import org.apache.skywalking.oap.query.promql.entity.MetricRangeData;
 import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
 import 
org.apache.skywalking.oap.query.promql.rt.exception.IllegalExpressionException;
@@ -33,10 +43,14 @@ import 
org.apache.skywalking.oap.server.core.query.PointOfTime;
 import org.apache.skywalking.oap.server.core.query.input.Duration;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
 import org.apache.skywalking.promql.rt.grammar.PromQLParser;
 import org.joda.time.format.PeriodFormatter;
 import org.joda.time.format.PeriodFormatterBuilder;
 
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+
 public class PromOpUtils {
 
     static MetricsRangeResult matrixScalarBinaryOp(MetricsRangeResult matrix, 
ScalarResult scalar, int opType) {
@@ -92,6 +106,70 @@ public class PromOpUtils {
         return result;
     }
 
+    static MetricsRangeResult matrixAggregateOp(MetricsRangeResult result, int 
funcType, List<String> groupingBy) {
+        List<MetricRangeData> metricDataList = result.getMetricDataList();
+        Map<List<LabelValuePair>, List<MetricRangeData>> groupedResult = 
metricDataList
+            .stream().collect(groupingBy(rangeData -> getLabels(groupingBy, 
rangeData), LinkedHashMap::new, toList()));
+
+        MetricsRangeResult rangeResult = new MetricsRangeResult();
+        rangeResult.setResultType(ParseResultType.METRICS_RANGE);
+        AggregateLabelsFuncFactory factory = getAggregateFuncFactory(funcType);
+        groupedResult.forEach((labels, dataList) -> {
+            if (dataList.isEmpty()) {
+                return;
+            }
+            List<TimeValuePair> combineTo = dataList.get(0).getValues();
+            for (int i = 0; i < combineTo.size(); i++) {
+                AggregateLabelsFunc aggregateLabelsFunc = 
factory.getAggregateLabelsFunc();
+                for (MetricRangeData rangeData : dataList) {
+                    TimeValuePair toCombine = rangeData.getValues().get(i);
+                    if (StringUtil.isNotBlank(toCombine.getValue())) {
+                        
aggregateLabelsFunc.combine(Double.parseDouble(toCombine.getValue()));
+                    }
+                }
+
+                TimeValuePair timeValuePair = combineTo.get(i);
+                Double aggResult = aggregateLabelsFunc.getResult();
+                if (aggResult != null) {
+                    timeValuePair.setValue(aggResult.toString());
+                }
+            }
+            MetricRangeData rangeData = new MetricRangeData();
+            rangeData.setMetric(new MetricInfo(null));
+            rangeData.getMetric().setLabels(labels);
+            rangeData.setValues(combineTo);
+            rangeResult.getMetricDataList().add(rangeData);
+        });
+
+        return rangeResult;
+    }
+
+    private static AggregateLabelsFuncFactory getAggregateFuncFactory(int 
funcType) {
+        switch (funcType) {
+            case PromQLParser.AVG:
+                return AvgAggregateLabelsFunc::new;
+            case PromQLParser.SUM:
+                return SumAggregateLabelsFunc::new;
+            case PromQLParser.MAX:
+                return MaxAggregateLabelsFunc::new;
+            case PromQLParser.MIN:
+                return MinAggregateLabelsFunc::new;
+            default:
+                throw new IllegalArgumentException("Unsupported aggregate 
function type: " + funcType);
+        }
+    }
+
+    private static List<LabelValuePair> getLabels(List<String> groupingBy, 
MetricRangeData data) {
+        return groupingBy.stream()
+                         .map(
+                             labelName ->
+                                 data.getMetric().getLabels()
+                                           .stream().filter(label -> 
labelName.equals(label.getLabelName()))
+                                           .findAny().orElseGet(() -> new 
LabelValuePair(labelName, ""))
+                         )
+                         .collect(toList());
+    }
+
     static double scalarBinaryOp(double leftValue, double rightValue, int 
opType) {
         double calculatedResult = 0;
         switch (opType) {
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
index e9570a590f..f46bf2ddd0 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
@@ -26,7 +26,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
+import org.antlr.v4.runtime.RuleContext;
 import org.apache.skywalking.oap.query.promql.entity.ErrorType;
 import org.apache.skywalking.oap.query.promql.entity.LabelName;
 import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
@@ -58,6 +60,7 @@ import 
org.apache.skywalking.oap.server.core.query.type.Record;
 import org.apache.skywalking.oap.server.core.query.type.SelectedRecord;
 import org.apache.skywalking.oap.server.core.storage.annotation.Column;
 import 
org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 import org.apache.skywalking.promql.rt.grammar.PromQLParser;
 import org.apache.skywalking.promql.rt.grammar.PromQLParserBaseVisitor;
@@ -135,6 +138,51 @@ public class PromQLExprQueryVisitor extends 
PromQLParserBaseVisitor<ParseResult>
         return compareOp(left, right, opType, boolModifier);
     }
 
+    @Override
+    public ParseResult visitAggregationOp(final 
PromQLParser.AggregationOpContext ctx) {
+        ParseResult parseResult = visit(ctx.expression());
+        if (StringUtil.isNotBlank(parseResult.getErrorInfo())) {
+            return parseResult;
+        }
+
+        if 
(!parseResult.getResultType().equals(ParseResultType.METRICS_RANGE)) {
+            ParseResult result = new ParseResult();
+            result.setErrorType(ErrorType.BAD_DATA);
+            result.setErrorInfo("Expected type instant vector in aggregation 
expression.");
+            return result;
+        }
+
+        MetricsRangeResult metricsRangeResult = (MetricsRangeResult) 
parseResult;
+        if (CollectionUtils.isEmpty(metricsRangeResult.getMetricDataList())) {
+            return metricsRangeResult;
+        }
+
+        List<String> resultLabelNames = metricsRangeResult.getMetricDataList()
+                                                          
.get(0).getMetric().getLabels()
+                                                          
.stream().map(LabelValuePair::getLabelName)
+                                                          
.collect(Collectors.toList());
+
+        List<String> groupingBy = new ArrayList<>();
+        PromQLParser.AggregationClauseContext clauseContext = 
ctx.aggregationClause();
+        if (clauseContext != null) {
+            List<String> clauseGroupingBy = 
clauseContext.labelNameList().labelName().stream()
+                                                         
.map(RuleContext::getText)
+                                                         
.filter(resultLabelNames::contains)
+                                                         
.collect(Collectors.toList());
+            if (clauseContext.getStart().getType() == PromQLParser.WITHOUT) {
+                groupingBy = resultLabelNames.stream()
+                                             .filter(labelName -> 
!clauseGroupingBy.contains(labelName))
+                                             .collect(Collectors.toList());
+            } else {
+                groupingBy = clauseGroupingBy;
+            }
+        }
+
+        return PromOpUtils.matrixAggregateOp(
+            (MetricsRangeResult) parseResult, 
ctx.aggregationFunc().getStart().getType(), groupingBy
+        );
+    }
+
     private ParseResult compareOp(ParseResult left, ParseResult right, int 
opType, boolean boolModifier) {
         try {
             if (left.getResultType() == ParseResultType.SCALAR && 
right.getResultType() == ParseResultType.SCALAR) {
diff --git 
a/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
 
b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
index 60109c8a64..e9876a180e 100644
--- 
a/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
+++ 
b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
@@ -25,6 +25,7 @@ import lombok.SneakyThrows;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
 import org.antlr.v4.runtime.tree.ParseTree;
+import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
 import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
 import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler;
 import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
@@ -121,6 +122,108 @@ public class PromQLExprQueryVisitorTest {
         });
     }
 
+    public static Collection<Object[]> aggregateData() {
+        // {service_instance_id=a,group=g} 0, 1, 2
+        // {service_instance_id=b,group=g} 2, 3, 4
+        return Arrays.asList(new Object[][] {
+            {
+                "MetricsAggregationOpSum",
+                PromQLApiHandler.QueryType.RANGE,
+                "sum by(group) (http_requests_total{service='serviceA', 
layer='GENERAL'})",
+                List.of(
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "2.0"),
+                        new TimeValuePair(TIME_2023022011, "4.0"),
+                        new TimeValuePair(TIME_2023022012, "6.0")
+                    )
+                ),
+                List.of(
+                    List.of(
+                        new LabelValuePair("group", "g")
+                    )
+                )
+            },
+            {
+                "MetricsAggregationOpAvg",
+                PromQLApiHandler.QueryType.RANGE,
+                "avg by(group) (http_requests_total{service='serviceA', 
layer='GENERAL'})",
+                List.of(
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "1.0"),
+                        new TimeValuePair(TIME_2023022011, "2.0"),
+                        new TimeValuePair(TIME_2023022012, "3.0")
+                    )
+                ),
+                List.of(
+                    List.of(
+                        new LabelValuePair("group", "g")
+                    )
+                )
+            },
+            {
+                "MetricsAggregationOpMax",
+                PromQLApiHandler.QueryType.RANGE,
+                "max (http_requests_total{service='serviceA', 
layer='GENERAL'}) by (group)",
+                List.of(
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "2.0"),
+                        new TimeValuePair(TIME_2023022011, "3.0"),
+                        new TimeValuePair(TIME_2023022012, "4.0")
+                    )
+                ),
+                List.of(
+                    List.of(
+                        new LabelValuePair("group", "g")
+                    )
+                )
+            },
+            {
+                "MetricsAggregationOpMin",
+                PromQLApiHandler.QueryType.RANGE,
+                "min (http_requests_total{service='serviceA', 
layer='GENERAL'}) by (group)",
+                List.of(
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "0.0"),
+                        new TimeValuePair(TIME_2023022011, "1.0"),
+                        new TimeValuePair(TIME_2023022012, "2.0")
+                    )
+                ),
+                List.of(
+                    List.of(
+                        new LabelValuePair("group", "g")
+                    )
+                )
+            },
+            {
+                "MetricsAggregationOpMinWithout",
+                PromQLApiHandler.QueryType.RANGE,
+                "min (http_requests_total{service='serviceA', 
layer='GENERAL'}) without (group)",
+                List.of(
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "0.0"),
+                        new TimeValuePair(TIME_2023022011, "1.0"),
+                        new TimeValuePair(TIME_2023022012, "2.0")
+                    ),
+                    List.of(
+                        new TimeValuePair(TIME_2023022010, "2.0"),
+                        new TimeValuePair(TIME_2023022011, "3.0"),
+                        new TimeValuePair(TIME_2023022012, "4.0")
+                    )
+                ),
+                List.of(
+                    List.of(
+                        new LabelValuePair("service_instance_id", "a"),
+                        new LabelValuePair("layer", "GENERAL")
+                    ),
+                    List.of(
+                        new LabelValuePair("service_instance_id", "b"),
+                        new LabelValuePair("layer", "GENERAL")
+                    )
+                )
+            }
+        });
+    }
+
     @SneakyThrows
     @BeforeEach
     public void setup() {
@@ -128,6 +231,10 @@ public class PromQLExprQueryVisitorTest {
                                                  0,
                                                  DefaultScopeDefine.SERVICE
         );
+        ValueColumnMetadata.INSTANCE.putIfAbsent("http_requests_total", 
"value", Column.ValueDataType.LABELED_VALUE,
+                                                 0,
+                                                 DefaultScopeDefine.SERVICE
+        );
         metricsQueryService = mock(MetricsQueryService.class);
         recordQueryService = mock(RecordQueryService.class);
         aggregationQueryService = mock(AggregationQueryService.class);
@@ -138,6 +245,10 @@ public class PromQLExprQueryVisitorTest {
         Mockito.doReturn(mockMetricsValues())
                .when(metricsQueryService)
                .readMetricsValues(any(MetricsCondition.class), 
any(Duration.class));
+
+        Mockito.doReturn(mockLabeledMetricsValues())
+               .when(metricsQueryService)
+               .readLabeledMetricsValues(any(MetricsCondition.class), any(), 
any(Duration.class));
     }
 
     private MetricsValues mockMetricsValues() {
@@ -152,6 +263,30 @@ public class PromQLExprQueryVisitorTest {
         return values;
     }
 
+    private List<MetricsValues> mockLabeledMetricsValues() {
+        final List<PointOfTime> pointOfTimes = 
duration.assembleDurationPoints();
+        // {service_instance_id=a,group=g} 0, 1, 2
+        MetricsValues values1 = new MetricsValues();
+        values1.setLabel("{service_instance_id=a,group=g}");
+        for (int i = 0; i < pointOfTimes.size(); i++) {
+            final KVInt kvInt = new KVInt();
+            kvInt.setId(String.valueOf(pointOfTimes.get(i).getPoint()));
+            kvInt.setValue(i);
+            values1.getValues().addKVInt(kvInt);
+        }
+
+        // {service_instance_id=b,group=g} 2, 3, 4
+        MetricsValues values2 = new MetricsValues();
+        values2.setLabel("{service_instance_id=b,group=g}");
+        for (int i = 0; i < pointOfTimes.size(); i++) {
+            final KVInt kvInt = new KVInt();
+            kvInt.setId(String.valueOf(pointOfTimes.get(i).getPoint()));
+            kvInt.setValue(i + 2);
+            values2.getValues().addKVInt(kvInt);
+        }
+        return List.of(values1, values2);
+    }
+
     @ParameterizedTest(name = "{0}")
     @MethodSource("data")
     public void test(String name,
@@ -182,4 +317,28 @@ public class PromQLExprQueryVisitorTest {
                 Assertions.fail();
         }
     }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("aggregateData")
+    public void testAggregate(String name,
+                              PromQLApiHandler.QueryType queryType,
+                              String expression,
+                              List<Object> wantResultValues,
+                              List<Object> wantResultLabels) {
+        PromQLLexer lexer = new 
PromQLLexer(CharStreams.fromString(expression));
+        CommonTokenStream tokens = new CommonTokenStream(lexer);
+        PromQLParser parser = new PromQLParser(tokens);
+        ParseTree tree = parser.expression();
+        PromQLExprQueryVisitor visitor = new PromQLExprQueryVisitor(
+            metricsQueryService, recordQueryService, aggregationQueryService, 
duration, queryType);
+        ParseResult parseResult = visitor.visit(tree);
+        Assertions.assertEquals(ParseResultType.METRICS_RANGE, 
parseResult.getResultType());
+
+        MetricsRangeResult result = (MetricsRangeResult) parseResult;
+        Assertions.assertEquals(result.getMetricDataList().size(), 
wantResultValues.size());
+        for (int i = 0; i < result.getMetricDataList().size(); i++) {
+            
Assertions.assertEquals(result.getMetricDataList().get(i).getValues(), 
wantResultValues.get(i));
+            
Assertions.assertEquals(result.getMetricDataList().get(i).getMetric().getLabels(),
 wantResultLabels.get(i));
+        }
+    }
 }
diff --git 
a/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-by-p.yml
 
b/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-by-p.yml
new file mode 100644
index 0000000000..d2ca97a281
--- /dev/null
+++ 
b/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-by-p.yml
@@ -0,0 +1,46 @@
+# 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.
+
+status: success
+data:
+  resultType: matrix
+  result:
+    {{- contains .data.result }}
+    - metric:
+        __name__:
+        p: 50
+      values:
+        {{- contains .values }}
+        - - "{{ index . 0 }}"
+          - "{{ index . 1 }}"
+        {{- end}}
+    - metric:
+        __name__:
+        p: 75
+      values:
+        {{- contains .values }}
+        - - "{{ index . 0 }}"
+          - "{{ index . 1 }}"
+        {{- end}}
+    - metric:
+        __name__:
+        p: 90
+      values:
+        {{- contains .values }}
+        - - "{{ index . 0 }}"
+          - "{{ index . 1 }}"
+        {{- end}}
+    {{- end}}
+
diff --git 
a/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-without-p.yml
 
b/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-without-p.yml
new file mode 100644
index 0000000000..3a9d566087
--- /dev/null
+++ 
b/test/e2e-v2/cases/promql/expected/service-metric-labeled-matrix-aggregate-without-p.yml
@@ -0,0 +1,32 @@
+# 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.
+
+status: success
+data:
+  resultType: matrix
+  result:
+    {{- contains .data.result }}
+    - metric:
+        __name__:
+        layer: GENERAL
+        scope: Service
+        service: e2e-service-consumer
+      values:
+        {{- contains .values }}
+        - - "{{ index . 0 }}"
+          - "{{ index . 1 }}"
+        {{- end}}
+    {{- end}}
+
diff --git a/test/e2e-v2/cases/promql/promql-cases.yaml 
b/test/e2e-v2/cases/promql/promql-cases.yaml
index 51adce32cb..c32dd53ff3 100644
--- a/test/e2e-v2/cases/promql/promql-cases.yaml
+++ b/test/e2e-v2/cases/promql/promql-cases.yaml
@@ -74,6 +74,10 @@ cases:
     expected: expected/service-metric-matrix.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_percentile{service="e2e-service-consumer", layer="GENERAL", 
p="50,75,90"}&start='$(($(date +%s)-1800))'&end='$(date +%s)
     expected: expected/service-metric-labeled-matrix.yml
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=sum by (p) (service_percentile{service="e2e-service-consumer", 
layer="GENERAL", p="50,75,90"})&start='$(($(date +%s)-1800))'&end='$(date +%s)
+    expected: expected/service-metric-labeled-matrix-aggregate-by-p.yml
+  - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=sum without (p) (service_percentile{service="e2e-service-consumer", 
layer="GENERAL", p="50,75,90"})&start='$(($(date +%s)-1800))'&end='$(date +%s)
+    expected: expected/service-metric-labeled-matrix-aggregate-without-p.yml
   - query: curl -X GET http://${oap_host}:${oap_9090}/api/v1/query_range -d 
'query=service_cpm{layer="GENERAL",top_n="10", order="DES"}&start='$(($(date 
+%s)-1800))'&end='$(date +%s)
     expected: expected/service-metric-sort-matrix.yml
   ## instance

Reply via email to