From b3044ee71464c117ffcbf80a5b669da0ad64c260 Mon Sep 17 00:00:00 2001 From: Wan Kai Date: Tue, 7 May 2024 16:42:14 +0800 Subject: [PATCH] MQE support `sort_values` and `sort_label_values` functions. (#12193) * MQE support `sort_values` and `sort_label_values` functions. * sync UI. --- docs/en/api/metrics-query-expression.md | 65 +++++++++- docs/en/changes/changes.md | 1 + .../skywalking/mqe/rt/grammar/MQELexer.g4 | 4 + .../skywalking/mqe/rt/grammar/MQEParser.g4 | 8 ++ .../skywalking/mqe/rt/MQEVisitorBase.java | 39 ++++++ .../mqe/rt/operation/SortLabelValuesOp.java | 106 ++++++++++++++++ .../mqe/rt/operation/SortValuesOp.java | 57 +++++++++ .../apache/skywalking/mqe/rt/MockData.java | 55 +++++++++ .../mqe/rt/SortLabelValuesOpTest.java | 115 ++++++++++++++++++ .../skywalking/mqe/rt/SortValuesOpTest.java | 66 ++++++++++ skywalking-ui | 2 +- .../mqe/expected/sort-label-value-OP.yml | 59 +++++++++ .../cases/mqe/expected/sort-value-OP.yml | 35 ++++++ test/e2e-v2/cases/mqe/mqe-cases.yaml | 11 ++ 14 files changed, 617 insertions(+), 6 deletions(-) create mode 100644 oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortLabelValuesOp.java create mode 100644 oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortValuesOp.java create mode 100644 oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortLabelValuesOpTest.java create mode 100644 oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortValuesOpTest.java create mode 100644 test/e2e-v2/cases/mqe/expected/sort-label-value-OP.yml create mode 100644 test/e2e-v2/cases/mqe/expected/sort-value-OP.yml diff --git a/docs/en/api/metrics-query-expression.md b/docs/en/api/metrics-query-expression.md index 95a7d88060..b43c615d0f 100644 --- a/docs/en/api/metrics-query-expression.md +++ b/docs/en/api/metrics-query-expression.md @@ -232,9 +232,8 @@ Expression: top_n(, , ) ``` -`top_number` is the number of the top results, should be a positive integer. - -`order` is the order of the top results. The value of `order` can be `asc` or `des`. +- `top_number` is the number of the top results, should be a positive integer. +- `order` is the order of the top results. The value of `order` can be `asc` or `des`. For example: If we want to query the current service's top 10 instances with the highest `service_instance_cpm` metric value, we can use the following expression @@ -273,11 +272,11 @@ AggregateLabels Operation takes an expression and performs an aggregate calculat Expression: ```text -aggregate_labels(Expression, AggregateType(,...)) +aggregate_labels(Expression, (,...)) ``` - `AggregateType` is the type of the aggregation operation. -- `,...` is the label names that need to be aggregated. If not specified, all labels will be aggregated. +- `,...` is the label names that need to be aggregated. If not specified, all labels will be aggregated. Optional. | AggregateType | Definition | ExpressionResultType | |---------------|----------------------------------------------------|----------------------| @@ -379,6 +378,62 @@ V(T1)-V(T1-2), V(T2)-V(T1-1), V(T3)-V(T1) ### Result Type TIME_SERIES_VALUES. +## Sort Operation +### SortValues Operation +SortValues Operation takes an expression and sorts the values of the input expression result. + +Expression: +```text +sort_values(Expression, , ) +``` +- `limit` is the number of the sort results, should be a positive integer, if not specified, will return all results. Optional. +- `order` is the order of the sort results. The value of `order` can be `asc` or `des`. + +For example: +If we want to sort the `service_resp_time` metric values in descending order and get the top 10 values, we can use the following expression: +```text +sort_values(service_resp_time, 10, des) +``` + +#### Result Type +The result type follows the input expression. + +### SortLabelValues Operation +SortLabelValues Operation takes an expression and sorts the label values of the input expression result. This function uses `natural sort order`. + +Expression: +```text +sort_label_values(Expression, , , ...) +``` +- `order` is the order of the sort results. The value of `order` can be `asc` or `des`. +- `, ...` is the label names that need to be sorted by their values. At least one label name should be specified. +The labels in the head of the list will be sorted first, and if the label not be included in the expression result will be ignored. + +For example: +If we want to sort the `service_percentile` metric label values in descending order by the `p` label, we can use the following expression: +```text +sort_label_values(service_percentile{p='50,75,90,95,99'}, des, p) +``` + +For multiple labels, assume the metric has 2 labels: +```text +metric{label1='a', label2='2a'} +metric{label1='a', label2='2c'} +metric{label1='b', label2='2a'} +metric{label1='b', label2='2c'} +``` +If we want to sort the `metric` metric label values in descending order by the `label1` and `label2` labels, we can use the following expression: +```text +sort_label_values(metric, des, label1, label2) +``` +And the result will be: +```text +metric{label1='b', label2='2c'} +metric{label1='b', label2='2a'} +metric{label1='a', label2='2c'} +metric{label1='a', label2='2a'} +``` + ## Expression Query Example ### Labeled Value Metrics ```text diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md index a14d03b635..8b9f28a1d2 100644 --- a/docs/en/changes/changes.md +++ b/docs/en/changes/changes.md @@ -120,6 +120,7 @@ * Add component definition(ID=152) for `c3p0`(JDBC3 Connection and Statement Pooling). * Fix MQE `top_n` global query. * Fix inaccurate Pulsar and Bookkeeper metrics. +* MQE support `sort_values` and `sort_label_values` functions. #### UI 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 7302c7ed7b..b91139ee85 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 @@ -81,6 +81,10 @@ DES options { caseInsensitive=true; }: 'des'; // AGGREGATE_LABELS AGGREGATE_LABELS: 'aggregate_labels'; +// Sort +SORT_VALUES: 'sort_values'; +SORT_LABEL_VALUES: 'sort_label_values'; + // Literals INTEGER: Digit+; DECIMAL: Digit+ DOT Digit+; 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 b8768392b8..d83c72365c 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 @@ -36,6 +36,8 @@ expression | topN L_PAREN metric COMMA INTEGER COMMA order R_PAREN #topNOP | relabels L_PAREN expression COMMA label COMMA replaceLabel R_PAREN #relablesOP | aggregateLabels L_PAREN expression COMMA aggregateLabelsFunc R_PAREN #aggregateLabelsOp + | sort_values L_PAREN expression (COMMA INTEGER)? COMMA order R_PAREN #sortValuesOP + | sort_label_values L_PAREN expression COMMA order COMMA labelNameList R_PAREN #sortLabelValuesOP ; expressionList @@ -91,3 +93,9 @@ aggregateLabelsFunc: aggregateLabelsFuncName (L_PAREN labelNameList R_PAREN)?; aggregateLabelsFuncName: AVG | SUM | MAX | MIN; + +sort_values: + SORT_VALUES; + +sort_label_values: + SORT_LABEL_VALUES; 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 23b2d677d6..4416176b83 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 @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.apache.skywalking.mqe.rt.grammar.MQEParser; import org.apache.skywalking.mqe.rt.grammar.MQEParserBaseVisitor; @@ -31,6 +32,8 @@ import org.apache.skywalking.mqe.rt.operation.BinaryOp; 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.SortLabelValuesOp; +import org.apache.skywalking.mqe.rt.operation.SortValuesOp; import org.apache.skywalking.mqe.rt.operation.TrendOp; import org.apache.skywalking.mqe.rt.type.ExpressionResult; import org.apache.skywalking.mqe.rt.exception.IllegalExpressionException; @@ -311,6 +314,42 @@ public abstract class MQEVisitorBase extends MQEParserBaseVisitor limit = Optional.empty(); + if (ctx.INTEGER() != null) { + limit = Optional.of(Integer.valueOf(ctx.INTEGER().getText())); + } + try { + return SortValuesOp.doSortValuesOp(result, limit, order); + } catch (IllegalExpressionException e) { + ExpressionResult errorResult = new ExpressionResult(); + errorResult.setType(ExpressionResultType.UNKNOWN); + errorResult.setError(e.getMessage()); + return errorResult; + } + } + + @Override + public ExpressionResult visitSortLabelValuesOP(MQEParser.SortLabelValuesOPContext ctx) { + ExpressionResult result = visit(ctx.expression()); + int order = ctx.order().getStart().getType(); + List labelNames = new ArrayList<>(); + for (MQEParser.LabelNameContext labelNameContext : ctx.labelNameList().labelName()) { + labelNames.add(labelNameContext.getText()); + } + try { + return SortLabelValuesOp.doSortLabelValuesOp(result, order, labelNames); + } catch (IllegalExpressionException e) { + ExpressionResult errorResult = new ExpressionResult(); + errorResult.setType(ExpressionResultType.UNKNOWN); + errorResult.setError(e.getMessage()); + return errorResult; + } + } + @Override public abstract ExpressionResult visitMetric(MQEParser.MetricContext ctx); diff --git a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortLabelValuesOp.java b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortLabelValuesOp.java new file mode 100644 index 0000000000..828e5e84a5 --- /dev/null +++ b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortLabelValuesOp.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.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +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.MQEValues; +import org.apache.skywalking.oap.server.core.query.type.KeyValue; +import org.apache.skywalking.oap.server.library.util.CollectionUtils; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + +@Slf4j +public class SortLabelValuesOp { + public static ExpressionResult doSortLabelValuesOp(ExpressionResult expResult, + int order, + List labelNames) throws IllegalExpressionException { + if (CollectionUtils.isNotEmpty(labelNames)) { + labelNames = labelNames.stream().distinct().collect(toList()); + if (MQEParser.ASC == order) { + expResult.setResults( + sort(expResult.getResults(), labelNames, labelNames.get(0), Comparator.naturalOrder())); + } else if (MQEParser.DES == order) { + expResult.setResults( + sort(expResult.getResults(), labelNames, labelNames.get(0), Comparator.reverseOrder())); + } else { + throw new IllegalExpressionException("Unsupported sort order."); + } + } + return expResult; + } + + private static List sort(List results, + List sortLabels, + String currentSortLabel, + Comparator comparator) { + if (!sortLabels.contains(currentSortLabel)) { + log.error("Current sort label {} not found in the sort labels {} ", currentSortLabel, sortLabels); + return results; + } + if (sortLabels.indexOf( + currentSortLabel) == sortLabels.size() - 1) { //only one label or the latest label no need to group + results.sort(Comparator.comparing(mqeValues -> mqeValues.getMetric() + .getLabels() + .stream() + .filter(kv -> kv.getKey().equals(currentSortLabel)) + .findFirst() + .orElse(new KeyValue(currentSortLabel, "")) + .getValue(), comparator)); + } else { + LinkedHashMap> groupResult = group(results, currentSortLabel); + LinkedHashMap> sortedGroup = new LinkedHashMap<>(groupResult.size()); + for (Map.Entry> entry : groupResult.entrySet()) { + //sort by next label + List sortedResult = sort( + entry.getValue(), sortLabels, sortLabels.get(sortLabels.indexOf(currentSortLabel) + 1), comparator); + sortedGroup.put(entry.getKey(), sortedResult); + } + //combine the sorted group + results = sortedGroup.keySet() + .stream() + .sorted(Comparator.comparing(KeyValue::getValue, comparator)) + .flatMap(keyValue -> sortedGroup.get(keyValue).stream()) + .collect(toList()); + } + return results; + } + + //group by current label for sub sorting + private static LinkedHashMap> group(List results, String labelName) { + return results + .stream() + .collect(groupingBy( + mqeValues -> mqeValues.getMetric().getLabels() + .stream() + .filter(kv -> kv.getKey().equals(labelName)) + .findFirst().orElse(new KeyValue(labelName, "")), + LinkedHashMap::new, + toList() + )); + } +} + diff --git a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortValuesOp.java b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortValuesOp.java new file mode 100644 index 0000000000..44053555f9 --- /dev/null +++ b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/SortValuesOp.java @@ -0,0 +1,57 @@ +/* + * 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.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +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; + +public class SortValuesOp { + public static ExpressionResult doSortValuesOp(ExpressionResult expResult, + Optional limit, + int order) throws IllegalExpressionException { + if (MQEParser.ASC == order || MQEParser.DES == order) { + expResult.getResults().forEach(mqeValues -> { + List values = mqeValues.getValues() + .stream() + // Filter out empty values + .filter(mqeValue -> !mqeValue.isEmptyValue()) + .sorted(MQEParser.ASC == order ? Comparator.comparingDouble( + MQEValue::getDoubleValue) : + Comparator.comparingDouble(MQEValue::getDoubleValue) + .reversed()) + .collect( + Collectors.toList()); + if (limit.isPresent() && limit.get() < values.size()) { + mqeValues.setValues(values.subList(0, limit.get())); + } else { + mqeValues.setValues(values); + } + }); + } else { + throw new IllegalExpressionException("Unsupported sort order."); + } + return expResult; + } +} diff --git a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/MockData.java b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/MockData.java index 92fa7a8a6e..2a53df8f0d 100644 --- a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/MockData.java +++ b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/MockData.java @@ -65,6 +65,61 @@ public class MockData { return seriesLabeled; } + public ExpressionResult newSeriesComplexLabeledResult() { + ExpressionResult seriesLabeled = new ExpressionResult(); + seriesLabeled.setLabeledResult(true); + seriesLabeled.setType(ExpressionResultType.TIME_SERIES_VALUES); + MQEValues mqeValues111 = new MQEValues(); + mqeValues111.setMetric(newMetadata("label", "a")); + mqeValues111.getMetric().getLabels().add(new KeyValue("label2", "2a")); + mqeValues111.getMetric().getLabels().add(new KeyValue("label3", "31")); + mqeValues111.getValues().add(newMQEValue("100", 100)); + MQEValues mqeValues112 = new MQEValues(); + mqeValues112.setMetric(newMetadata("label", "a")); + mqeValues112.getMetric().getLabels().add(new KeyValue("label2", "2a")); + mqeValues112.getMetric().getLabels().add(new KeyValue("label3", "32")); + mqeValues112.getValues().add(newMQEValue("100", 101)); + MQEValues mqeValues121 = new MQEValues(); + mqeValues121.setMetric(newMetadata("label", "a")); + mqeValues121.getMetric().getLabels().add(new KeyValue("label2", "2b")); + mqeValues121.getMetric().getLabels().add(new KeyValue("label3", "31")); + mqeValues121.getValues().add(newMQEValue("100", 102)); + MQEValues mqeValues122 = new MQEValues(); + mqeValues122.setMetric(newMetadata("label", "a")); + mqeValues122.getMetric().getLabels().add(new KeyValue("label2", "2b")); + mqeValues122.getMetric().getLabels().add(new KeyValue("label3", "32")); + mqeValues122.getValues().add(newMQEValue("100", 103)); + MQEValues mqeValues211 = new MQEValues(); + mqeValues211.setMetric(newMetadata("label", "b")); + mqeValues211.getMetric().getLabels().add(new KeyValue("label2", "2a")); + mqeValues211.getMetric().getLabels().add(new KeyValue("label3", "31")); + mqeValues211.getValues().add(newMQEValue("100", 104)); + MQEValues mqeValues212 = new MQEValues(); + mqeValues212.setMetric(newMetadata("label", "b")); + mqeValues212.getMetric().getLabels().add(new KeyValue("label2", "2a")); + mqeValues212.getMetric().getLabels().add(new KeyValue("label3", "32")); + mqeValues212.getValues().add(newMQEValue("100", 105)); + MQEValues mqeValues221 = new MQEValues(); + mqeValues221.setMetric(newMetadata("label", "b")); + mqeValues221.getMetric().getLabels().add(new KeyValue("label2", "2b")); + mqeValues221.getMetric().getLabels().add(new KeyValue("label3", "31")); + mqeValues221.getValues().add(newMQEValue("100", 106)); + MQEValues mqeValues222 = new MQEValues(); + mqeValues222.setMetric(newMetadata("label", "b")); + mqeValues222.getMetric().getLabels().add(new KeyValue("label2", "2b")); + mqeValues222.getMetric().getLabels().add(new KeyValue("label3", "32")); + mqeValues222.getValues().add(newMQEValue("100", 107)); + seriesLabeled.getResults().add(mqeValues111); + seriesLabeled.getResults().add(mqeValues112); + seriesLabeled.getResults().add(mqeValues121); + seriesLabeled.getResults().add(mqeValues122); + seriesLabeled.getResults().add(mqeValues211); + seriesLabeled.getResults().add(mqeValues212); + seriesLabeled.getResults().add(mqeValues221); + seriesLabeled.getResults().add(mqeValues222); + return seriesLabeled; + } + public ExpressionResult newSeriesLabeledResult(double id1001, double id3001, double id1002, double id3002) { ExpressionResult seriesLabeled = new ExpressionResult(); seriesLabeled.setLabeledResult(true); diff --git a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortLabelValuesOpTest.java b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortLabelValuesOpTest.java new file mode 100644 index 0000000000..61ab428bcc --- /dev/null +++ b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortLabelValuesOpTest.java @@ -0,0 +1,115 @@ +/* + * 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 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.operation.SortLabelValuesOp; +import org.apache.skywalking.mqe.rt.type.ExpressionResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SortLabelValuesOpTest { + private final MockData mockData = new MockData(); + + @Test + public void sortLabelValueTest() throws IllegalExpressionException { + //des + ExpressionResult des = SortLabelValuesOp.doSortLabelValuesOp(mockData.newSeriesComplexLabeledResult(), + MQEParser.DES, List.of("label", "label2", "label3") + ); + assertDes(des); + //asc + ExpressionResult asc = SortLabelValuesOp.doSortLabelValuesOp(des, + MQEParser.ASC, List.of("label", "label2", "label3") + ); + assertAsc(asc); + // distinct + ExpressionResult distinctDes = SortLabelValuesOp.doSortLabelValuesOp(mockData.newSeriesComplexLabeledResult(), + MQEParser.DES, List.of("label", "label2", "label3", "label") + ); + assertDes(distinctDes); + // ignore + ExpressionResult ignoreDes = SortLabelValuesOp.doSortLabelValuesOp(mockData.newSeriesComplexLabeledResult(), + MQEParser.DES, List.of("label", "labelIgnore", "label2", "label3") + ); + assertDes(ignoreDes); + } + + private void assertDes(ExpressionResult des) { + //label + Assertions.assertEquals("b", des.getResults().get(0).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", des.getResults().get(1).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", des.getResults().get(2).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", des.getResults().get(3).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", des.getResults().get(4).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", des.getResults().get(5).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", des.getResults().get(6).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", des.getResults().get(7).getMetric().getLabels().get(0).getValue()); + // label2 + Assertions.assertEquals("2b", des.getResults().get(0).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", des.getResults().get(1).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", des.getResults().get(2).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", des.getResults().get(3).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", des.getResults().get(4).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", des.getResults().get(5).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", des.getResults().get(6).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", des.getResults().get(7).getMetric().getLabels().get(1).getValue()); + // label3 + Assertions.assertEquals("32", des.getResults().get(0).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", des.getResults().get(1).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", des.getResults().get(2).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", des.getResults().get(3).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", des.getResults().get(4).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", des.getResults().get(5).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", des.getResults().get(6).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", des.getResults().get(7).getMetric().getLabels().get(2).getValue()); + } + + private void assertAsc(ExpressionResult asc) { + //label + Assertions.assertEquals("a", asc.getResults().get(0).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", asc.getResults().get(1).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", asc.getResults().get(2).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("a", asc.getResults().get(3).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", asc.getResults().get(4).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", asc.getResults().get(5).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", asc.getResults().get(6).getMetric().getLabels().get(0).getValue()); + Assertions.assertEquals("b", asc.getResults().get(7).getMetric().getLabels().get(0).getValue()); + // label2 + Assertions.assertEquals("2a", asc.getResults().get(0).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", asc.getResults().get(1).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", asc.getResults().get(2).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", asc.getResults().get(3).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", asc.getResults().get(4).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2a", asc.getResults().get(5).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", asc.getResults().get(6).getMetric().getLabels().get(1).getValue()); + Assertions.assertEquals("2b", asc.getResults().get(7).getMetric().getLabels().get(1).getValue()); + // label3 + Assertions.assertEquals("31", asc.getResults().get(0).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", asc.getResults().get(1).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", asc.getResults().get(2).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", asc.getResults().get(3).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", asc.getResults().get(4).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", asc.getResults().get(5).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("31", asc.getResults().get(6).getMetric().getLabels().get(2).getValue()); + Assertions.assertEquals("32", asc.getResults().get(7).getMetric().getLabels().get(2).getValue()); + } +} diff --git a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortValuesOpTest.java b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortValuesOpTest.java new file mode 100644 index 0000000000..fd964b2666 --- /dev/null +++ b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/SortValuesOpTest.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.Optional; +import org.apache.skywalking.mqe.rt.exception.IllegalExpressionException; +import org.apache.skywalking.mqe.rt.grammar.MQEParser; +import org.apache.skywalking.mqe.rt.operation.SortValuesOp; +import org.apache.skywalking.mqe.rt.type.ExpressionResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class SortValuesOpTest { + private final MockData mockData = new MockData(); + + @Test + public void sortValueTest() throws IllegalExpressionException { + //no label + ExpressionResult des = SortValuesOp.doSortValuesOp(mockData.newSeriesNoLabeledResult(), Optional.of(3), + MQEParser.DES); + Assertions.assertEquals(300, des.getResults().get(0).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(100, des.getResults().get(0).getValues().get(1).getDoubleValue()); + ExpressionResult asc = SortValuesOp.doSortValuesOp(mockData.newSeriesNoLabeledResult(), Optional.of(3), + MQEParser.ASC); + Assertions.assertEquals(100, asc.getResults().get(0).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(300, asc.getResults().get(0).getValues().get(1).getDoubleValue()); + + //labeled + ExpressionResult desLabeled = SortValuesOp.doSortValuesOp(mockData.newSeriesLabeledResult(), Optional.of(3), + MQEParser.DES); + Assertions.assertEquals(300, desLabeled.getResults().get(0).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(100, desLabeled.getResults().get(0).getValues().get(1).getDoubleValue()); + Assertions.assertEquals(301, desLabeled.getResults().get(1).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(101, desLabeled.getResults().get(1).getValues().get(1).getDoubleValue()); + ExpressionResult ascLabeled = SortValuesOp.doSortValuesOp(mockData.newSeriesLabeledResult(), Optional.of(2), + MQEParser.ASC); + Assertions.assertEquals(100, ascLabeled.getResults().get(0).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(300, ascLabeled.getResults().get(0).getValues().get(1).getDoubleValue()); + Assertions.assertEquals(101, ascLabeled.getResults().get(1).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(301, ascLabeled.getResults().get(1).getValues().get(1).getDoubleValue()); + + //limit + ExpressionResult desLabeledLimit = SortValuesOp.doSortValuesOp(mockData.newSeriesLabeledResult(), Optional.of(1), + MQEParser.DES); + Assertions.assertEquals(1, desLabeledLimit.getResults().get(0).getValues().size()); + Assertions.assertEquals(1, desLabeledLimit.getResults().get(1).getValues().size()); + Assertions.assertEquals(300, desLabeledLimit.getResults().get(0).getValues().get(0).getDoubleValue()); + Assertions.assertEquals(301, desLabeledLimit.getResults().get(1).getValues().get(0).getDoubleValue()); + } +} diff --git a/skywalking-ui b/skywalking-ui index 13b2693f29..e4b2203cf6 160000 --- a/skywalking-ui +++ b/skywalking-ui @@ -1 +1 @@ -Subproject commit 13b2693f291903349ebaffa661092e2747141f1c +Subproject commit e4b2203cf6f33b1fd84b79ced30ee7c50e15e6d8 diff --git a/test/e2e-v2/cases/mqe/expected/sort-label-value-OP.yml b/test/e2e-v2/cases/mqe/expected/sort-label-value-OP.yml new file mode 100644 index 0000000000..5fedeb3c0a --- /dev/null +++ b/test/e2e-v2/cases/mqe/expected/sort-label-value-OP.yml @@ -0,0 +1,59 @@ +# 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: + - key: p + value: "90" + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ .value }} + traceid: null + - id: {{ notEmpty .id }} + value: null + traceid: null + {{- end}} +- metric: + labels: + - key: p + value: "75" + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ .value }} + traceid: null + - id: {{ notEmpty .id }} + value: null + traceid: null + {{- end}} +- metric: + labels: + - key: p + value: "50" + 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/expected/sort-value-OP.yml b/test/e2e-v2/cases/mqe/expected/sort-value-OP.yml new file mode 100644 index 0000000000..ee5bd130dd --- /dev/null +++ b/test/e2e-v2/cases/mqe/expected/sort-value-OP.yml @@ -0,0 +1,35 @@ +# 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: + {{- contains .metric.labels }} + - key: p + value: {{ .value }} + {{- end}} + values: + {{- contains .values }} + - id: {{ notEmpty .id }} + value: {{ .value }} + traceid: null + - id: {{ notEmpty .id }} + value: {{ .value }} + 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 1973369343..772bb75665 100644 --- a/test/e2e-v2/cases/mqe/mqe-cases.yaml +++ b/test/e2e-v2/cases/mqe/mqe-cases.yaml @@ -81,3 +81,14 @@ cases: 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 + + # sort-OP e2e used for test MQE expression, more tests can refer to the UT + # sort-value-OP + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="sort_values(service_percentile,2,asc)" --service-name=e2e-service-provider + expected: expected/sort-value-OP.yml + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="sort_values(service_percentile,des)" --service-name=e2e-service-provider + expected: expected/sort-value-OP.yml + + # sort-label-value-OP + - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics exec --expression="sort_label_values(service_percentile{p='50,75,90'},des,p)" --service-name=e2e-service-provider + expected: expected/sort-label-value-OP.yml