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 0a0890c148 MQE: Support `top_n_of` function for merging multiple
metrics topn query. (#12937)
0a0890c148 is described below
commit 0a0890c148e3139df33142c2270db4d098570872
Author: Wan Kai <[email protected]>
AuthorDate: Wed Jan 8 12:53:34 2025 +0800
MQE: Support `top_n_of` function for merging multiple metrics topn query.
(#12937)
---
docs/en/api/metrics-query-expression.md | 20 ++++++
docs/en/changes/changes.md | 1 +
.../apache/skywalking/mqe/rt/grammar/MQELexer.g4 | 1 +
.../apache/skywalking/mqe/rt/grammar/MQEParser.g4 | 6 +-
.../apache/skywalking/mqe/rt/MQEVisitorBase.java | 23 ++++++-
.../skywalking/mqe/rt/operation/TopNOfOp.java | 71 ++++++++++++++++++++++
.../org/apache/skywalking/mqe/rt/TopNOfOpTest.java | 59 ++++++++++++++++++
.../oap/query/graphql/mqe/rt/MQEVisitor.java | 8 +--
test/e2e-v2/cases/mqe/mqe-cases.yaml | 5 +-
9 files changed, 186 insertions(+), 8 deletions(-)
diff --git a/docs/en/api/metrics-query-expression.md
b/docs/en/api/metrics-query-expression.md
index 28e28f367d..1446368fa7 100644
--- a/docs/en/api/metrics-query-expression.md
+++ b/docs/en/api/metrics-query-expression.md
@@ -217,6 +217,7 @@ round(service_cpm / 60 , 2)
The different operators could impact the `ExpressionResultType`, please refer
to the above table.
## TopN Operation
+### TopN Query
TopN Operation takes an expression and performs calculation to get the TopN of
Services/Instances/Endpoints.
The result depends on the `entity` condition in the query.
- Global TopN:
@@ -264,6 +265,25 @@ top_n(service_instance_cpm, 10, des)
### Result Type
According to the type of the metric, the `ExpressionResultType` of the
expression will be `SORTED_LIST` or `RECORD_LIST`.
+### Multiple TopNs Merging
+As the difference between agent and ebpf, some metrics would be separated,
e.g. service cpm and k8s service cpm.
+If you want to merge the topN results of these metrics, you can use the
`ton_n_of` operation to merge the results.
+
+expression:
+```text
+ton_n_of(<top_n>, <top_n>, ...,<top_number>, <order>)
+```
+
+- `<top_n>` is the [topN](#topn-query) expression. The result type of those
tonN expression should be same, can be `SORTED_LIST` or `RECORD_LIST`, `but can
not be mixed`.
+- `<top_number>` is the number of the merged top results, should be a positive
integer.
+- `<order>` is the order of the merged top results. The value of `<order>` can
be `asc` or `des`.
+
+for example:
+If we want to get the top 10 services with the highest `service_cpm` and
`k8s_service_cpm`, we can use the following expression:
+```text
+ton_n_of(top_n(service_cpm, 10, des), top_n(k8s_service_cpm, 10, des), 10, des)
+```
+
## Relabel Operation
Relabel Operation takes an expression and replaces the label with new label on
its results.
Since v10.0.0, SkyWalking supports relabel multiple labels.
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 3001b77ee1..ea5ee772b0 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -53,6 +53,7 @@
* Add `bydb.dependencies.properties` config file to define server dependency
versions.
* Fix `AvgHistogramPercentileFunction` doesn't have proper field definition
for `ranks`.
* BanyanDB: Support the new Property data module.
+* MQE: Support `top_n_of` function for merging multiple metrics topn query.
#### 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 0ea98355b1..dbea0ce0bf 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
@@ -64,6 +64,7 @@ RATE: 'rate';
// TopN
TOP_N: 'top_n';
+TOP_N_OF: 'top_n_of';
// ViewAsSeq
VIEW_AS_SEQ: 'view_as_seq';
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 d85edcde40..d1a23a2bd5 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
@@ -33,7 +33,8 @@ expression
| 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 INTEGER COMMA order (COMMA attributeList)?
R_PAREN #topNOP
+ | topN
#topNOP
+ | topNOf L_PAREN topN (COMMA topN)* COMMA INTEGER COMMA order R_PAREN
#topNOfOP
| 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
@@ -75,7 +76,8 @@ mathematical_operator1:
trend:
INCREASE | RATE;
-topN: TOP_N;
+topN: TOP_N L_PAREN metric COMMA INTEGER COMMA order (COMMA attributeList)?
R_PAREN;
+topNOf: TOP_N_OF;
logical_operator:
VIEW_AS_SEQ | IS_PRESENT;
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 fcc41dc194..d15cb11943 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
@@ -34,6 +34,7 @@ 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.TopNOfOp;
import org.apache.skywalking.mqe.rt.operation.TrendOp;
import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResult;
import org.apache.skywalking.mqe.rt.exception.IllegalExpressionException;
@@ -240,7 +241,27 @@ public abstract class MQEVisitorBase extends
MQEParserBaseVisitor<ExpressionResu
DebuggingTraceContext traceContext = TRACE_CONTEXT.get();
DebuggingSpan span = traceContext.createSpan("MQE TopN OP: " +
ctx.getText());
try {
- return visit(ctx.metric());
+ return visit(ctx.topN().metric());
+ } finally {
+ traceContext.stopSpan(span);
+ }
+ }
+
+ @Override
+ public ExpressionResult visitTopNOfOP(MQEParser.TopNOfOPContext ctx) {
+ DebuggingTraceContext traceContext = TRACE_CONTEXT.get();
+ DebuggingSpan span = traceContext.createSpan("MQE TopNOf OP: " +
ctx.getText());
+ try {
+ List<MQEParser.TopNContext> topNContexts = ctx.topN();
+ List<ExpressionResult> topNResults = new ArrayList<>();
+ for (MQEParser.TopNContext topNContext : topNContexts) {
+ topNResults.add(visit(topNContext.metric()));
+ }
+ try {
+ return TopNOfOp.doMergeTopNResult(topNResults,
Integer.parseInt(ctx.INTEGER().getText()), ctx.order().getStart().getType());
+ } catch (IllegalExpressionException e) {
+ return getErrorResult(e.getMessage());
+ }
} finally {
traceContext.stopSpan(span);
}
diff --git
a/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TopNOfOp.java
b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TopNOfOp.java
new file mode 100644
index 0000000000..478e6cbdae
--- /dev/null
+++
b/oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/TopNOfOp.java
@@ -0,0 +1,71 @@
+/*
+ * 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.Comparator;
+import java.util.List;
+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.oap.server.core.query.mqe.ExpressionResult;
+import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResultType;
+import org.apache.skywalking.oap.server.core.query.mqe.MQEValue;
+import org.apache.skywalking.oap.server.core.query.mqe.MQEValues;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+public class TopNOfOp {
+ public static ExpressionResult doMergeTopNResult(List<ExpressionResult>
topNResults,
+ int limit,
+ int order) throws
IllegalExpressionException {
+ ExpressionResultType type = null;
+ List<MQEValue> allValues = new ArrayList<>();
+ for (ExpressionResult topNResult : topNResults) {
+ if (StringUtil.isNotEmpty(topNResult.getError())) {
+ return topNResult;
+ }
+ // check the type of topNResults
+ if (type != null && type != topNResult.getType()) {
+ throw new IllegalExpressionException("TopN type is not
consistent, one is " + type + ", another is " +
+
topNResult.getType());
+ }
+ type = topNResult.getType();
+ // topN result should have values without label
+ allValues.addAll(topNResult.getResults().get(0).getValues());
+ }
+ if (limit > allValues.size()) {
+ limit = allValues.size();
+ }
+ List<MQEValue> mergedValues = allValues.stream()
+ // Filter out empty values
+ .filter(mqeValue ->
!mqeValue.isEmptyValue())
+ .sorted(MQEParser.ASC == order
? Comparator.comparingDouble(
+ MQEValue::getDoubleValue) :
+
Comparator.comparingDouble(MQEValue::getDoubleValue)
+
.reversed())
+
.limit(limit).collect(Collectors.toList());
+
+ ExpressionResult result = new ExpressionResult();
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.setValues(mergedValues);
+ result.getResults().add(mqeValues);
+ result.setType(type);
+ return result;
+ }
+}
diff --git
a/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TopNOfOpTest.java
b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TopNOfOpTest.java
new file mode 100644
index 0000000000..ec5c77fe05
--- /dev/null
+++
b/oap-server/mqe-rt/src/test/java/org/apache/skywalking/mqe/rt/TopNOfOpTest.java
@@ -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.
+ *
+ */
+
+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.TopNOfOp;
+import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResult;
+import org.apache.skywalking.oap.server.core.query.mqe.ExpressionResultType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TopNOfOpTest {
+
+ @Test
+ public void mergeTopNResultTest() throws IllegalExpressionException {
+ MockData mockData = new MockData();
+ List<ExpressionResult> topNResults = List.of(
+ mockData.newListResult(1000, 100),
+ mockData.newListResult(600, 500),
+ mockData.newListResult(300, 2000)
+ );
+ ExpressionResult topNResult = TopNOfOp.doMergeTopNResult(topNResults,
2, MQEParser.DES);
+ Assertions.assertEquals(ExpressionResultType.SORTED_LIST,
topNResult.getType());
+ Assertions.assertEquals(2,
topNResult.getResults().get(0).getValues().size());
+ Assertions.assertEquals(2000,
topNResult.getResults().get(0).getValues().get(0).getDoubleValue());
+ Assertions.assertEquals("service_B",
topNResult.getResults().get(0).getValues().get(0).getId());
+ Assertions.assertEquals(1000,
topNResult.getResults().get(0).getValues().get(1).getDoubleValue());
+ Assertions.assertEquals("service_A",
topNResult.getResults().get(0).getValues().get(1).getId());
+
+ ExpressionResult topNResultAsc =
TopNOfOp.doMergeTopNResult(topNResults, 8, MQEParser.ASC);
+ Assertions.assertEquals(6,
topNResultAsc.getResults().get(0).getValues().size());
+ Assertions.assertEquals(100,
topNResultAsc.getResults().get(0).getValues().get(0).getDoubleValue());
+ Assertions.assertEquals("service_B",
topNResultAsc.getResults().get(0).getValues().get(0).getId());
+ Assertions.assertEquals(2000,
topNResultAsc.getResults().get(0).getValues().get(5).getDoubleValue());
+ Assertions.assertEquals("service_B",
topNResultAsc.getResults().get(0).getValues().get(5).getId());
+ topNResults.get(2).setType(ExpressionResultType.RECORD_LIST);
+ Assertions.assertThrows(IllegalExpressionException.class, () -> {
+ TopNOfOp.doMergeTopNResult(topNResults, 2, MQEParser.DES);
+ });
+ }
+}
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 ed7290e905..0d09ecf0e1 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
@@ -124,8 +124,8 @@ public class MQEVisitor extends MQEVisitorBase {
Column.ValueDataType dataType = valueColumn.get().getDataType();
try {
if (Column.ValueDataType.COMMON_VALUE == dataType) {
- if (ctx.parent instanceof MQEParser.TopNOPContext) {
- MQEParser.TopNOPContext parent =
(MQEParser.TopNOPContext) ctx.parent;
+ if (ctx.parent instanceof MQEParser.TopNContext) {
+ MQEParser.TopNContext parent = (MQEParser.TopNContext)
ctx.parent;
int topN =
Integer.parseInt(parent.INTEGER().getText());
if (topN <= 0) {
throw new IllegalExpressionException("TopN value
must be > 0.");
@@ -170,8 +170,8 @@ public class MQEVisitor extends MQEVisitorBase {
queryLabeledMetrics(metricName, queryLabels,
this.duration, result);
}
} else if (Column.ValueDataType.SAMPLED_RECORD == dataType) {
- if (ctx.parent instanceof MQEParser.TopNOPContext) {
- MQEParser.TopNOPContext parent =
(MQEParser.TopNOPContext) ctx.parent;
+ if (ctx.parent instanceof MQEParser.TopNContext) {
+ MQEParser.TopNContext parent = (MQEParser.TopNContext)
ctx.parent;
int topN =
Integer.parseInt(parent.INTEGER().getText());
if (topN <= 0) {
throw new IllegalExpressionException("TopN value
must be > 0.");
diff --git a/test/e2e-v2/cases/mqe/mqe-cases.yaml
b/test/e2e-v2/cases/mqe/mqe-cases.yaml
index 0805f2f34b..53a231ad50 100644
--- a/test/e2e-v2/cases/mqe/mqe-cases.yaml
+++ b/test/e2e-v2/cases/mqe/mqe-cases.yaml
@@ -45,10 +45,13 @@ cases:
- query: swctl --display yaml
--base-url=http://${oap_host}:${oap_12800}/graphql metrics exec
--expression="top_n(service_resp_time,3,des,attr0!='Not_GENERAL')/100"
expected: expected/topN-OP-service.yml
- # topN-OP-isntance
+ # topN-OP-instance
- query: swctl --display yaml
--base-url=http://${oap_host}:${oap_12800}/graphql metrics exec
--expression="top_n(service_instance_resp_time,3,des)/100"
--service-name=e2e-service-provider
expected: expected/topN-OP-instance.yml
+ # topN-Of-OP
+ - query: swctl --display yaml
--base-url=http://${oap_host}:${oap_12800}/graphql metrics exec
--expression="top_n_of(top_n(service_resp_time,3,des,attr0='GENERAL'),
top_n(service_cpm,3,des,attr0='GENERAL'), 2, des)"
+ expected: expected/topN-OP-service.yml
# select labels and relabels
- query: swctl --display yaml
--base-url=http://${oap_host}:${oap_12800}/graphql metrics exec
--expression="relabels(relabels(service_percentile{p='50,75,90'},p='50,75',p='P50,P75'),p='90',p='P90')"
--service-name=e2e-service-provider
expected: expected/relabels-OP.yml