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 0f847438c7 Support Metrics Query Expression(MQE) and allows users to
do simple query-stage calculation through the expression. (#10881)
0f847438c7 is described below
commit 0f847438c780f99ce1b50fe7c585a0884280f33a
Author: Wan Kai <[email protected]>
AuthorDate: Mon Jun 5 17:22:05 2023 +0800
Support Metrics Query Expression(MQE) and allows users to do simple
query-stage calculation through the expression. (#10881)
---
docs/en/api/metrics-query-expression.md | 124 ++++++
docs/en/api/query-protocol-deprecated.md | 84 ++++
docs/en/api/query-protocol.md | 97 ++---
docs/en/changes/changes.md | 4 +
docs/menu.yml | 8 +-
.../oap/server/core/query/type/SelectedRecord.java | 2 +-
.../server-query-plugin/{ => mqe-grammar}/pom.xml | 38 +-
.../apache/skywalking/mqe/rt/grammar/MQELexer.g4 | 82 ++++
.../apache/skywalking/mqe/rt/grammar/MQEParser.g4 | 67 ++++
oap-server/server-query-plugin/pom.xml | 1 +
.../oap/query/promql/handler/PromQLApiHandler.java | 16 +-
.../promql/rt/exception/ParseErrorListener.java | 2 -
.../query-graphql-plugin/pom.xml | 5 +
.../oap/query/graphql/GraphQLQueryProvider.java | 14 +-
.../oap/query/graphql/mqe/rt/MQEVisitor.java | 426 +++++++++++++++++++++
.../rt/exception/IllegalExpressionException.java} | 31 +-
.../mqe}/rt/exception/ParseErrorListener.java | 4 +-
.../graphql/mqe/rt/operation/AggregationOp.java | 111 ++++++
.../query/graphql/mqe/rt/operation/BinaryOp.java | 131 +++++++
.../query/graphql/mqe/rt/operation/FunctionOp.java | 72 ++++
.../graphql/resolver/MetricsExpressionQuery.java | 98 +++++
.../query/graphql/type/mql/ExpressionResult.java | 31 ++
.../graphql/type/mql/ExpressionResultType.java | 46 +++
.../oap/query/graphql/type/mql/MQEValue.java | 32 ++
.../oap/query/graphql/type/mql/MQEValues.java | 34 ++
.../oap/query/graphql/type/mql/Metadata.java | 35 ++
.../src/main/resources/query-protocol | 2 +-
skywalking-ui | 2 +-
28 files changed, 1479 insertions(+), 120 deletions(-)
diff --git a/docs/en/api/metrics-query-expression.md
b/docs/en/api/metrics-query-expression.md
new file mode 100644
index 0000000000..87b0561389
--- /dev/null
+++ b/docs/en/api/metrics-query-expression.md
@@ -0,0 +1,124 @@
+# Metrics Query Expression(MQE) Syntax
+MQE is a string that consists of one or more expressions. Each expression
could be a combination of one or more operations.
+The expression allows users to do simple query-stage calculation through [MQE
APIs](./query-protocol.md#mqe-apis).
+
+```text
+Expression = <Operation> Expression1 <Operation> Expression2 <Operation>
Expression3 ...
+```
+
+The following document lists the operations supported by MQE.
+
+## Metrics Expression
+### Common Value Metrics
+Expression:
+```text
+<metric_name>
+```
+
+For example:
+If we want to query the `service_sla` metric, we can use the following
expression:
+```text
+service_sla
+```
+
+### Labeled Value Metrics
+Expression:
+```text
+<metric_name>{labels='<label_name1>,...', relabels='<new_label_name1>,...'}
+```
+`labels` is the selected labels name of the metric, and `relabels` is the
labels name of the metric after the label is renamed.
+
+If `labels` is not specified, all labels of the metric will be selected. And
if `relabels` is not specified, the label name will not be changed.
+
+For example:
+If we want to query the `service_percentile` metric with the labels
`0,1,2,3,4`, and rename the labels to `P50,P75,P90,P95,P99`, we can use the
following expression:
+```text
+service_percentile{labels='0,1,2,3,4', relabels='P50,P75,P90,P95,P99'}
+```
+
+
+## Binary Operation
+Binary Operation is an operation that takes two expressions and performs a
calculation on their results.
+The following table lists the binary operations supported by MQE.
+
+Expression:
+```text
+Expression1 <Binary-Operator> Expression2
+```
+
+| Operator | Definition |
+|----------|----------------------|
+| + | addition |
+| - | subtraction |
+| * | multiplication |
+| / | division |
+| % | modulo |
+
+For example:
+If we want to transform the service_sla metric value to percent, we can use
the following expression:
+```text
+service_sla / 100
+```
+
+## Aggregation Operation
+Aggregation Operation takes an expression and performs aggregate calculation
on its results.
+
+
+Expression:
+```text
+<Aggregation-Operator>(Expression)
+```
+
+
+| Operator | Definition |
+|----------|--------------------------------|
+| avg | average the result |
+| count | count number of the result |
+| sum | sum the result |
+| max | select maximum from the result |
+| min | select minimum from the result |
+
+For example:
+If we want to query the average value of the `service_cpm` metric, we can use
the following expression:
+
+```text
+avg(service_cpm)
+```
+
+
+## Function Operation
+Function Operation takes an expression and performs function calculation on
its results.
+
+<Function-Operator>(Expression, parameters)
+
+| Operator | Definition
| parameters |
+|----------|---------------------------------------------------------------------------|--------------------------------------------------------------------|
+| abs | returns the absolute value of the result
| |
+| ceil | returns the smallest integer value that is greater or equal to
the result |
|
+| floor | returns the largest integer value that is greater or equal to the
result | |
+| round | returns result round to specific decimal places
| `places`: a positive integer specific decimal places of the result |
+
+For example:
+If we want to query the average value of the `service_cpm` metric in seconds,
+and round the result to 2 decimal places, we can use the following expression:
+
+```text
+round(service_cpm / 60 , 2)
+```
+
+## TopN Operation
+TopN Operation takes an expression and performs TopN calculation on its
results.
+```text
+top_n(<metric_name>, <top_number>, <order>)
+```
+
+`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 top 10 services with the highest `service_cpm` metric
value, we can use the following expression:
+
+```text
+top_n(service_instance_cpm, 10, des)
+```
diff --git a/docs/en/api/query-protocol-deprecated.md
b/docs/en/api/query-protocol-deprecated.md
new file mode 100644
index 0000000000..e6a2226a11
--- /dev/null
+++ b/docs/en/api/query-protocol-deprecated.md
@@ -0,0 +1,84 @@
+# Deprecated Query Protocol
+The following query services are deprecated since 9.5.0. All these queries are
still available for the short term to keep compatibility.
+
+Query protocol official repository,
https://github.com/apache/skywalking-query-protocol.
+
+### Metrics
+Metrics query targets all objects defined in [OAL
script](../concepts-and-designs/oal.md) and
[MAL](../concepts-and-designs/mal.md).
+You may obtain the metrics data in linear or thermodynamic matrix formats
based on the aggregation functions in script.
+
+#### V2 APIs
+Provide Metrics V2 query APIs since 8.0.0, including metadata, single/multiple
values, heatmap, and sampled records metrics.
+```graphql
+extend type Query {
+ # Read metrics single value in the duration of required metrics
+ readMetricsValue(condition: MetricsCondition!, duration: Duration!): Long!
+ # Read metrics single value in the duration of required metrics
+ # NullableValue#isEmptyValue == true indicates no telemetry data rather
than aggregated value is actually zero.
+ readNullableMetricsValue(condition: MetricsCondition!, duration:
Duration!): NullableValue!
+ # Read time-series values in the duration of required metrics
+ readMetricsValues(condition: MetricsCondition!, duration: Duration!):
MetricsValues!
+ # Read entity list of required metrics and parent entity type.
+ sortMetrics(condition: TopNCondition!, duration: Duration!):
[SelectedRecord!]!
+ # Read value in the given time duration, usually as a linear.
+ # labels: the labels you need to query.
+ readLabeledMetricsValues(condition: MetricsCondition!, labels: [String!]!,
duration: Duration!): [MetricsValues!]!
+ # Heatmap is bucket based value statistic result.
+ readHeatMap(condition: MetricsCondition!, duration: Duration!): HeatMap
+ # Deprecated since 9.3.0, replaced by readRecords defined in
record.graphqls
+ # Read the sampled records
+ # TopNCondition#scope is not required.
+ readSampledRecords(condition: TopNCondition!, duration: Duration!):
[SelectedRecord!]!
+}
+```
+
+#### V1 APIs
+3 types of metrics can be queried. V1 APIs were introduced since 6.x. Now they
are a shell to V2 APIs.
+1. Single value. Most default metrics are in single value. `getValues` and
`getLinearIntValues` are suitable for this purpose.
+2. Multiple value. A metric defined in OAL includes multiple value
calculations. Use `getMultipleLinearIntValues` to obtain all values.
`percentile` is a typical multiple value function in OAL.
+3. Heatmap value. Read [Heatmap in
WIKI](https://en.wikipedia.org/wiki/Heat_map) for details. `thermodynamic` is
the only OAL function. Use `getThermodynamic` to get the values.
+```graphql
+extend type Query {
+ getValues(metric: BatchMetricConditions!, duration: Duration!): IntValues
+ getLinearIntValues(metric: MetricCondition!, duration: Duration!):
IntValues
+ # Query the type of metrics including multiple values, and format them as
multiple lines.
+ # The seq of these multiple lines base on the calculation func in OAL
+ # Such as, should us this to query the result of func
percentile(50,75,90,95,99) in OAL,
+ # then five lines will be responded, p50 is the first element of return
value.
+ getMultipleLinearIntValues(metric: MetricCondition!, numOfLinear: Int!,
duration: Duration!): [IntValues!]!
+ getThermodynamic(metric: MetricCondition!, duration: Duration!):
Thermodynamic
+}
+```
+
+### Aggregation
+Aggregation query means that the metrics data need a secondary aggregation at
query stage, which causes the query
+interfaces to have some different arguments. A typical example of aggregation
query is the `TopN` list of services.
+Metrics stream aggregation simply calculates the metrics values of each
service, but the expected list requires ordering metrics data
+by their values.
+
+Aggregation query is for single value metrics only.
+
+```graphql
+# The aggregation query is different with the metric query.
+# All aggregation queries require backend or/and storage do aggregation in
query time.
+extend type Query {
+ # TopN is an aggregation query.
+ getServiceTopN(name: String!, topN: Int!, duration: Duration!, order:
Order!): [TopNEntity!]!
+ getAllServiceInstanceTopN(name: String!, topN: Int!, duration: Duration!,
order: Order!): [TopNEntity!]!
+ getServiceInstanceTopN(serviceId: ID!, name: String!, topN: Int!,
duration: Duration!, order: Order!): [TopNEntity!]!
+ getAllEndpointTopN(name: String!, topN: Int!, duration: Duration!, order:
Order!): [TopNEntity!]!
+ getEndpointTopN(serviceId: ID!, name: String!, topN: Int!, duration:
Duration!, order: Order!): [TopNEntity!]!
+}
+```
+
+### Record
+Record is a general and abstract type for collected raw data.
+In the observability, traces and logs have specific and well-defined meanings,
meanwhile, the general records represent other
+collected records. Such as sampled slow SQL statement, HTTP request raw
data(request/response header/body)
+
+```graphql
+extend type Query {
+ # Query collected records with given metric name and parent entity
conditions, and return in the requested order.
+ readRecords(condition: RecordCondition!, duration: Duration!): [Record!]!
+}
+```
diff --git a/docs/en/api/query-protocol.md b/docs/en/api/query-protocol.md
index fb7eb6a50c..95cccd8bf5 100644
--- a/docs/en/api/query-protocol.md
+++ b/docs/en/api/query-protocol.md
@@ -4,6 +4,8 @@ native visualization tool or 3rd party system, including Web
UI, CLI or private
Query protocol official repository,
https://github.com/apache/skywalking-query-protocol.
+All deprecated APIs are moved [here](./query-protocol-deprecated.md).
+
### Metadata
Metadata contains concise information on all services and their instances,
endpoints, etc. under monitoring.
You may query the metadata in different ways.
@@ -68,11 +70,13 @@ extend type Query {
```
### Metrics
-Metrics query targets all objects defined in [OAL
script](../concepts-and-designs/oal.md) and
[MAL](../concepts-and-designs/mal.md).
-You may obtain the metrics data in linear or thermodynamic matrix formats
based on the aggregation functions in script.
+Metrics query targets all objects defined in [OAL
script](../concepts-and-designs/oal.md) and
[MAL](../concepts-and-designs/mal.md).
+
+#### V3 APIs
+Provide Metrics V3 query APIs since 9.5.0, including metadata and MQE.
+SkyWalking Metrics Query Expression(MQE) is an extension query mechanism. MQE
allows users to do simple query-stage calculation like well known PromQL
+through GraphQL. The expression's syntax can refer to
[here](./metrics-query-expression.md).
-#### V2 APIs
-Provide Metrics V2 query APIs since 8.0.0, including metadata, single/multiple
values, heatmap, and sampled records metrics.
```graphql
extend type Query {
# Metrics definition metadata query. Response the metrics type which
determines the suitable query methods.
@@ -80,75 +84,38 @@ extend type Query {
# Get the list of all available metrics in the current OAP server.
# Param, regex, could be used to filter the metrics by name.
listMetrics(regex: String): [MetricDefinition!]!
-
- # Read metrics single value in the duration of required metrics
- readMetricsValue(condition: MetricsCondition!, duration: Duration!): Long!
- # Read time-series values in the duration of required metrics
- readMetricsValues(condition: MetricsCondition!, duration: Duration!):
MetricsValues!
- # Read entity list of required metrics and parent entity type.
- sortMetrics(condition: TopNCondition!, duration: Duration!):
[SelectedRecord!]!
- # Read value in the given time duration, usually as a linear.
- # labels: the labels you need to query.
- readLabeledMetricsValues(condition: MetricsCondition!, labels: [String!]!,
duration: Duration!): [MetricsValues!]!
- # Heatmap is bucket based value statistic result.
- readHeatMap(condition: MetricsCondition!, duration: Duration!): HeatMap
- # Deprecated since 9.3.0, replaced by readRecords defined in
record.graphqls
- # Read the sampled records
- # TopNCondition#scope is not required.
- readSampledRecords(condition: TopNCondition!, duration: Duration!):
[SelectedRecord!]!
-}
-```
-
-#### V1 APIs
-3 types of metrics can be queried. V1 APIs were introduced since 6.x. Now they
are a shell to V2 APIs.
-1. Single value. Most default metrics are in single value. `getValues` and
`getLinearIntValues` are suitable for this purpose.
-1. Multiple value. A metric defined in OAL includes multiple value
calculations. Use `getMultipleLinearIntValues` to obtain all values.
`percentile` is a typical multiple value function in OAL.
-1. Heatmap value. Read [Heatmap in
WIKI](https://en.wikipedia.org/wiki/Heat_map) for details. `thermodynamic` is
the only OAL function. Use `getThermodynamic` to get the values.
-```graphql
-extend type Query {
- getValues(metric: BatchMetricConditions!, duration: Duration!): IntValues
- getLinearIntValues(metric: MetricCondition!, duration: Duration!):
IntValues
- # Query the type of metrics including multiple values, and format them as
multiple lines.
- # The seq of these multiple lines base on the calculation func in OAL
- # Such as, should us this to query the result of func
percentile(50,75,90,95,99) in OAL,
- # then five lines will be responded, p50 is the first element of return
value.
- getMultipleLinearIntValues(metric: MetricCondition!, numOfLinear: Int!,
duration: Duration!): [IntValues!]!
- getThermodynamic(metric: MetricCondition!, duration: Duration!):
Thermodynamic
+ # The return type of the given expression, the MQEValues will be empty.
+ returnTypeOfMQE(expression: String!): ExpressionResult!
+ execExpression(expression: String!, entity: Entity!, duration: Duration!):
ExpressionResult!
}
```
-Metrics are defined in the `config/oal/*.oal` files.
-
-### Aggregation
-Aggregation query means that the metrics data need a secondary aggregation at
query stage, which causes the query
-interfaces to have some different arguments. A typical example of aggregation
query is the `TopN` list of services.
-Metrics stream aggregation simply calculates the metrics values of each
service, but the expected list requires ordering metrics data
-by their values.
-
-Aggregation query is for single value metrics only.
-
```graphql
-# The aggregation query is different with the metric query.
-# All aggregation queries require backend or/and storage do aggregation in
query time.
-extend type Query {
- # TopN is an aggregation query.
- getServiceTopN(name: String!, topN: Int!, duration: Duration!, order:
Order!): [TopNEntity!]!
- getAllServiceInstanceTopN(name: String!, topN: Int!, duration: Duration!,
order: Order!): [TopNEntity!]!
- getServiceInstanceTopN(serviceId: ID!, name: String!, topN: Int!,
duration: Duration!, order: Order!): [TopNEntity!]!
- getAllEndpointTopN(name: String!, topN: Int!, duration: Duration!, order:
Order!): [TopNEntity!]!
- getEndpointTopN(serviceId: ID!, name: String!, topN: Int!, duration:
Duration!, order: Order!): [TopNEntity!]!
+type ExpressionResult {
+ type: ExpressionResultType!
+ # When the type == TIME_SERIES_VALUES, the results would be a collection
of MQEValues.
+ # In other legal type cases, only one MQEValues is expected in the array.
+ results: [MQEValues!]!
+ # When type == ExpressionResultType.UNKNOWN,
+ # the error message includes the expression resolving errors.
+ error: String
}
```
-### Record
-Record is a general and abstract type for collected raw data.
-In the observability, traces and logs have specific and well-defined meanings,
meanwhile, the general records represent other
-collected records. Such as sampled slow SQL statement, HTTP request raw
data(request/response header/body)
-
```graphql
-extend type Query {
- # Query collected records with given metric name and parent entity
conditions, and return in the requested order.
- readRecords(condition: RecordCondition!, duration: Duration!): [Record!]!
+enum ExpressionResultType {
+ # Can't resolve the type of the given expression.
+ UNKNOWN
+ # A single value
+ SINGLE_VALUE
+ # A collection of time-series values.
+ # The value could have labels or not.
+ TIME_SERIES_VALUES
+ # A collection of aggregated values through metric sort function
+ SORTED_LIST
+ # A collection of sampled records.
+ # When the original metric type is sampled records
+ RECORD_LIST
}
```
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index ade246a373..03d4b663d5 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -48,6 +48,9 @@
* Support Redis slow logs collection.
* Fix data loss when query continuous profiling task record.
* Adapt the continuous profiling task query GraphQL.
+* Support Metrics Query Expression(MQE) and allows users to do simple
query-stage calculation through the expression.
+* Deprecated metrics query v2 protocol.
+* Deprecated record query protocol.
#### UI
* Revert: cpm5d function. This feature is cancelled from backend.
@@ -77,6 +80,7 @@
* Add grizzly icon.
* Fix: the Instance List data display error.
* Fix: set topN type to Number.
+* Support Metrics Query Expression(MQE) and allows users to do simple
query-stage calculation through the expression.
#### Documentation
diff --git a/docs/menu.yml b/docs/menu.yml
index 25b93f08a1..8d1532820f 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -292,7 +292,13 @@ catalog:
- name: "Query APIs"
catalog:
- name: "GraphQL APIs"
- path: "/en/api/query-protocol"
+ catalog:
+ - name: "GraphQL APIs"
+ path: "/en/api/query-protocol"
+ - name: "MQE Syntax"
+ path: "/en/api/metrics-query-expression"
+ - name: "Deprecated APIs"
+ path: "/en/api/query-protocol-deprecated"
- name: "PromQL APIs"
path: "/en/api/promql-service"
- name: "Security Notice"
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
index e85ffe52ba..84128c8782 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
@@ -22,7 +22,7 @@ import lombok.Getter;
import lombok.Setter;
/**
- * SelectedRecord is an abtract data element, including id, name, value and a
reference id.
+ * SelectedRecord is an abstract data element, including id, name, value and a
reference id.
*/
@Setter
@Getter
diff --git a/oap-server/server-query-plugin/pom.xml
b/oap-server/server-query-plugin/mqe-grammar/pom.xml
similarity index 57%
copy from oap-server/server-query-plugin/pom.xml
copy to oap-server/server-query-plugin/mqe-grammar/pom.xml
index ecc90f79a1..176876c0e9 100644
--- a/oap-server/server-query-plugin/pom.xml
+++ b/oap-server/server-query-plugin/mqe-grammar/pom.xml
@@ -19,17 +19,39 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
- <artifactId>oap-server</artifactId>
+ <artifactId>server-query-plugin</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>9.5.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>server-query-plugin</artifactId>
- <packaging>pom</packaging>
- <modules>
- <module>query-graphql-plugin</module>
- <module>zipkin-query-plugin</module>
- <module>promql-plugin</module>
- </modules>
+ <artifactId>mqe-grammar</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-runtime</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>antlr</id>
+ <goals>
+ <goal>antlr4</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <visitor>true</visitor>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git
a/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
new file mode 100644
index 0000000000..137cb01210
--- /dev/null
+++
b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQELexer.g4
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ *
+ */
+
+lexer grammar MQELexer;
+
+// Keywords
+BOOL options { caseInsensitive=true; }: 'bool';
+LABLES: 'labels';
+RELABELS: 'relabels';
+
+
+// Constructors symbols
+DOT: '.';
+COMMA: ',';
+L_PAREN: '(';
+R_PAREN: ')';
+L_BRACKET: '[';
+R_BRACKET: ']';
+L_BRACE: '{';
+R_BRACE: '}';
+EQ: '=';
+
+// Scalar Binary operators
+SUB: '-';
+ADD: '+';
+MUL: '*';
+DIV: '/';
+MOD: '%';
+DEQ: '==';
+NEQ: '!=';
+LTE: '<=';
+LT: '<';
+GTE: '>=';
+GT: '>';
+
+// Aggregation operators
+AVG: 'avg';
+COUNT: 'count';
+MAX: 'max';
+MIN: 'min';
+SUM: 'sum';
+
+// Functions
+ABS: 'abs';
+CEIL: 'ceil';
+FLOOR: 'floor';
+ROUND: 'round';
+
+// TopN
+TOP_N: 'top_n';
+
+// Order
+ASC options { caseInsensitive=true; }: 'asc';
+DES options { caseInsensitive=true; }: 'des';
+
+// Literals
+INTEGER: Digit+;
+DECIMAL: Digit+ DOT Digit+;
+NAME_STRING: NameLetter+;
+VALUE_STRING: '\'' .*? '\'' | '"' .*? '"';
+
+
+// Fragments
+fragment Digit: [0-9];
+fragment NameLetter: [a-zA-Z0-9_];
+
+WS : [ \t\r\n]+ -> skip;
diff --git
a/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
new file mode 100644
index 0000000000..8e463639c6
--- /dev/null
+++
b/oap-server/server-query-plugin/mqe-grammar/src/main/antlr4/org/apache/skywalking/mqe/rt/grammar/MQEParser.g4
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ *
+ */
+
+parser grammar MQEParser;
+
+options { tokenVocab = MQELexer; }
+
+root: expression;
+
+expression
+ : expressionNode # exprNode
+ | expression mulDivMod expression # mulDivModOp
+ | expression addSub expression # addSubOp
+ | expression compare expression # compareOp
+ | aggregation L_PAREN expression R_PAREN # aggregationOp
+ | function0 L_PAREN expression R_PAREN #function0OP
+ | function1 L_PAREN expression COMMA parameter R_PAREN #function1OP
+ | topN L_PAREN metric COMMA parameter COMMA order R_PAREN #topNOP
+ ;
+
+expressionNode: metric| scalar;
+
+addSub: ADD | SUB ;
+mulDivMod: MUL | DIV | MOD;
+compare: (DEQ | NEQ | LTE | LT | GTE | GT) BOOL?;
+
+metricName: NAME_STRING;
+metric: metricName | metricName L_BRACE labelList? R_BRACE;
+
+labelName: LABLES | RELABELS;
+labelValue: VALUE_STRING;
+label: labelName EQ labelValue;
+labelList: label (COMMA label)*;
+
+
+scalar: INTEGER | DECIMAL;
+
+aggregation:
+ AVG | COUNT | SUM | MAX | MIN ;
+
+// 0 parameter function
+function0:
+ ABS | CEIL | FLOOR;
+// 1 parameter function
+function1:
+ ROUND;
+
+topN: TOP_N;
+
+parameter: INTEGER;
+
+order: ASC | DES;
diff --git a/oap-server/server-query-plugin/pom.xml
b/oap-server/server-query-plugin/pom.xml
index ecc90f79a1..f637d6adc1 100644
--- a/oap-server/server-query-plugin/pom.xml
+++ b/oap-server/server-query-plugin/pom.xml
@@ -30,6 +30,7 @@
<modules>
<module>query-graphql-plugin</module>
<module>zipkin-query-plugin</module>
+ <module>mqe-grammar</module>
<module>promql-plugin</module>
</modules>
</project>
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 3d24153216..247deede7c 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
@@ -137,9 +137,9 @@ public class PromQLApiHandler {
if (match.isPresent()) {
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(match.get()));
- lexer.addErrorListener(ParseErrorListener.INSTANCE);
+ lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new
CommonTokenStream(lexer));
- parser.addErrorListener(ParseErrorListener.INSTANCE);
+ parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@@ -217,9 +217,9 @@ public class PromQLApiHandler {
SeriesQueryRsp response = new SeriesQueryRsp();
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(match));
- lexer.addErrorListener(ParseErrorListener.INSTANCE);
+ lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
- parser.addErrorListener(ParseErrorListener.INSTANCE);
+ parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@@ -295,9 +295,9 @@ public class PromQLApiHandler {
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(query));
- lexer.addErrorListener(ParseErrorListener.INSTANCE);
+ lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
- parser.addErrorListener(ParseErrorListener.INSTANCE);
+ parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
@@ -356,9 +356,9 @@ public class PromQLApiHandler {
ExprQueryRsp response = new ExprQueryRsp();
PromQLLexer lexer = new PromQLLexer(
CharStreams.fromString(query));
- lexer.addErrorListener(ParseErrorListener.INSTANCE);
+ lexer.addErrorListener(new ParseErrorListener());
PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
- parser.addErrorListener(ParseErrorListener.INSTANCE);
+ parser.addErrorListener(new ParseErrorListener());
ParseTree tree;
try {
tree = parser.expression();
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 9bab42e86f..e54481c458 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
@@ -24,8 +24,6 @@ import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
public class ParseErrorListener extends BaseErrorListener {
- public static final ParseErrorListener INSTANCE = new ParseErrorListener();
-
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/pom.xml
b/oap-server/server-query-plugin/query-graphql-plugin/pom.xml
index d7f31c6c2f..b190b318a0 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/pom.xml
+++ b/oap-server/server-query-plugin/query-graphql-plugin/pom.xml
@@ -43,6 +43,11 @@
<artifactId>log-analyzer</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.skywalking</groupId>
+ <artifactId>mqe-grammar</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
index 47f040ac26..e0c9ead3ab 100644
---
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
@@ -37,6 +37,7 @@ import
org.apache.skywalking.oap.query.graphql.resolver.LogTestQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetadataQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetadataQueryV2;
import org.apache.skywalking.oap.query.graphql.resolver.MetricQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.MetricsExpressionQuery;
import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
import org.apache.skywalking.oap.query.graphql.resolver.Mutation;
import org.apache.skywalking.oap.query.graphql.resolver.OndemandLogQuery;
@@ -98,11 +99,13 @@ public class GraphQLQueryProvider extends ModuleProvider {
.file("query-protocol/topology.graphqls")
.resolvers(new TopologyQuery(getManager()))
/*
- * Metrics v2 query protocol is an alternative metrics
query(s) of original v1,
- * defined in the metric.graphql, top-n-records.graphqls,
and aggregation.graphqls.
+ * Since 9.5.0.
+ * Metrics v3 query protocol is an enhanced metrics
query(s) from original v1 and v2
+ * powered by newly added Metrics Query Expression
Language to fetch and
+ * manipulate metrics data in the query stage.
*/
- .file("query-protocol/metrics-v2.graphqls")
- .resolvers(new MetricsQuery(getManager()))
+ .file("query-protocol/metrics-v3.graphqls")
+ .resolvers(new MetricsExpressionQuery(getManager()))
////////
//Deprecated Queries
////////
@@ -112,6 +115,9 @@ public class GraphQLQueryProvider extends ModuleProvider {
.resolvers(new AggregationQuery(getManager()))
.file("query-protocol/top-n-records.graphqls")
.resolvers(new TopNRecordsQuery(getManager()))
+ //Deprecated since 9.5.0
+ .file("query-protocol/metrics-v2.graphqls")
+ .resolvers(new MetricsQuery(getManager()))
////////
.file("query-protocol/trace.graphqls")
.resolvers(new TraceQuery(getManager()))
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
new file mode 100644
index 0000000000..62453cab41
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/MQEVisitor.java
@@ -0,0 +1,426 @@
+/*
+ * 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.oap.query.graphql.mqe.rt;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+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.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.graphql.mqe.rt.operation.AggregationOp;
+import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
+import org.apache.skywalking.oap.query.graphql.type.mql.Metadata;
+import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.query.DurationUtils;
+import org.apache.skywalking.oap.server.core.query.PointOfTime;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.Entity;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.input.RecordCondition;
+import org.apache.skywalking.oap.server.core.query.input.TopNCondition;
+import org.apache.skywalking.oap.server.core.query.type.KVInt;
+import org.apache.skywalking.oap.server.core.query.type.KeyValue;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+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.StringUtil;
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import org.apache.skywalking.mqe.rt.grammar.MQEParserBaseVisitor;
+
+import static
org.apache.skywalking.oap.query.graphql.mqe.rt.operation.BinaryOp.doBinaryOp;
+import static
org.apache.skywalking.oap.query.graphql.mqe.rt.operation.FunctionOp.doFunction0Op;
+import static
org.apache.skywalking.oap.query.graphql.mqe.rt.operation.FunctionOp.doFunction1Op;
+
+@Slf4j
+public class MQEVisitor extends MQEParserBaseVisitor<ExpressionResult> {
+ private final MetricsQuery metricsQuery;
+ private final RecordsQuery recordsQuery;
+ private final Entity entity;
+ private final Duration duration;
+
+ public MQEVisitor(final MetricsQuery metricsQuery,
+ final RecordsQuery recordsQuery,
+ final Entity entity,
+ final Duration duration) {
+ this.metricsQuery = metricsQuery;
+ this.recordsQuery = recordsQuery;
+ this.entity = entity;
+ this.duration = duration;
+ }
+
+ /**
+ * This constructor is only used for parsing expresstions, won't query any
metrics from backend.
+ */
+ public MQEVisitor() {
+ this.metricsQuery = null;
+ this.recordsQuery = null;
+ this.entity = null;
+ this.duration = null;
+ }
+
+ @Override
+ public ExpressionResult visitAddSubOp(MQEParser.AddSubOpContext ctx) {
+ ExpressionResult left = visit(ctx.expression(0));
+ if (StringUtil.isNotBlank(left.getError())) {
+ return left;
+ }
+ ExpressionResult right = visit(ctx.expression(1));
+ if (StringUtil.isNotBlank(right.getError())) {
+ return right;
+ }
+ int opType = ctx.addSub().getStart().getType();
+ try {
+ return doBinaryOp(left, right, opType);
+ } catch (IllegalExpressionException e) {
+ ExpressionResult result = new ExpressionResult();
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError(e.getMessage());
+ return result;
+ }
+ }
+
+ @Override
+ public ExpressionResult visitMulDivModOp(MQEParser.MulDivModOpContext ctx)
{
+ ExpressionResult left = visit(ctx.expression(0));
+ if (StringUtil.isNotBlank(left.getError())) {
+ return left;
+ }
+ ExpressionResult right = visit(ctx.expression(1));
+ if (StringUtil.isNotBlank(right.getError())) {
+ return right;
+ }
+ int opType = ctx.mulDivMod().getStart().getType();
+ try {
+ return doBinaryOp(left, right, opType);
+ } catch (IllegalExpressionException e) {
+ ExpressionResult result = new ExpressionResult();
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError(e.getMessage());
+ return result;
+ }
+ }
+
+ @Override
+ public ExpressionResult visitScalar(MQEParser.ScalarContext ctx) {
+ ExpressionResult result = new ExpressionResult();
+ double value = Double.parseDouble(ctx.getText());
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setDoubleValue(value);
+ mqeValue.setEmptyValue(false);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.getValues().add(mqeValue);
+ result.getResults().add(mqeValues);
+ result.setType(ExpressionResultType.SINGLE_VALUE);
+ return result;
+ }
+
+ @Override
+ public ExpressionResult visitAggregationOp(MQEParser.AggregationOpContext
ctx) {
+ int opType = ctx.aggregation().getStart().getType();
+ ExpressionResult expResult = visit(ctx.expression());
+ if (StringUtil.isNotEmpty(expResult.getError())) {
+ return expResult;
+ }
+ try {
+ return AggregationOp.doAggregationOp(expResult, opType);
+ } catch (IllegalExpressionException e) {
+ ExpressionResult result = new ExpressionResult();
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError(e.getMessage());
+ return result;
+ }
+ }
+
+ @Override
+ public ExpressionResult visitFunction0OP(MQEParser.Function0OPContext ctx)
{
+ int opType = ctx.function0().getStart().getType();
+ ExpressionResult expResult = visit(ctx.expression());
+ if (StringUtil.isNotEmpty(expResult.getError())) {
+ return expResult;
+ }
+ try {
+ return doFunction0Op(expResult, opType);
+ } catch (IllegalExpressionException e) {
+ ExpressionResult result = new ExpressionResult();
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError(e.getMessage());
+ return result;
+ }
+ }
+
+ @Override
+ public ExpressionResult visitFunction1OP(MQEParser.Function1OPContext ctx)
{
+ int opType = ctx.function1().getStart().getType();
+ ExpressionResult expResult = visit(ctx.expression());
+ if (StringUtil.isNotEmpty(expResult.getError())) {
+ return expResult;
+ }
+ try {
+ return doFunction1Op(expResult, opType, ctx.parameter());
+ } catch (IllegalExpressionException e) {
+ ExpressionResult result = new ExpressionResult();
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError(e.getMessage());
+ return result;
+ }
+ }
+
+ @Override
+ public ExpressionResult visitTopNOP(MQEParser.TopNOPContext ctx) {
+ return visit(ctx.metric());
+ }
+
+ @Override
+ public ExpressionResult visitMetric(MQEParser.MetricContext ctx) {
+ ExpressionResult result = new ExpressionResult();
+ String metricName = ctx.metricName().getText();
+ Optional<ValueColumnMetadata.ValueColumn> valueColumn =
ValueColumnMetadata.INSTANCE.readValueColumnDefinition(
+ metricName);
+ if (valueColumn.isEmpty()) {
+ result.setType(ExpressionResultType.UNKNOWN);
+ result.setError("Metric: [" + metricName + "] dose not exist.");
+ return result;
+ }
+
+ 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;
+ querySortMetrics(metricName,
Integer.parseInt(parent.parameter().getText()),
+
Order.valueOf(parent.order().getText().toUpperCase()), result
+ );
+ } else {
+ queryMetrics(metricName, result);
+ }
+ } else if (Column.ValueDataType.LABELED_VALUE == dataType) {
+ List<String> queryLabelList = Collections.emptyList();
+ List<String> relabelList = Collections.emptyList();
+ if (ctx.labelList() != null) {
+ for (MQEParser.LabelContext labelCtx :
ctx.labelList().label()) {
+ int labelName =
labelCtx.labelName().getStart().getType();
+ String labelValue = labelCtx.labelValue().getText();
+ String labelValueTrim = labelValue.substring(1,
labelValue.length() - 1);
+ switch (labelName) {
+ case MQEParser.LABLES:
+ if (StringUtil.isNotBlank(labelValueTrim)) {
+ queryLabelList =
Arrays.asList(labelValueTrim.split(Const.COMMA));
+ }
+ break;
+ case MQEParser.RELABELS:
+ if (StringUtil.isNotBlank(labelValueTrim)) {
+ relabelList =
Arrays.asList(labelValueTrim.split(Const.COMMA));
+ }
+ break;
+ }
+ }
+ }
+ queryLabeledMetrics(metricName, queryLabelList, relabelList,
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()),
+ Order.valueOf(parent.order().getText()),
result
+ );
+ } else {
+ throw new IllegalExpressionException(
+ "Metric: [" + metricName + "] is topN record, need
top_n function for query.");
+ }
+ }
+ } catch (IllegalExpressionException e) {
+ ExpressionResult errorResult = new ExpressionResult();
+ errorResult.setType(ExpressionResultType.UNKNOWN);
+ errorResult.setError(e.getMessage());
+ return errorResult;
+ } catch (IOException e) {
+ ExpressionResult errorResult = new ExpressionResult();
+ errorResult.setType(ExpressionResultType.UNKNOWN);
+ errorResult.setError("Internal IO exception, query metrics
error.");
+ log.error("Query metrics from backend error.", e);
+ return errorResult;
+ }
+ return result;
+ }
+
+ private void querySortMetrics(String metricName,
+ int topN,
+ Order order,
+ ExpressionResult result) throws IOException {
+ List<SelectedRecord> selectedRecords;
+ // API `returnTypeOfMQE` not require entity and duration.
+ if (entity != null && duration != null && metricsQuery != null) {
+ TopNCondition topNCondition = new TopNCondition();
+ topNCondition.setName(metricName);
+ topNCondition.setTopN(topN);
+ topNCondition.setParentService(entity.getServiceName());
+ topNCondition.setOrder(order);
+ topNCondition.setNormal(entity.getNormal());
+ selectedRecords = metricsQuery.sortMetrics(topNCondition,
duration);
+ } else {
+ selectedRecords = Collections.emptyList();
+ }
+ List<MQEValue> mqeValueList = new ArrayList<>(selectedRecords.size());
+ selectedRecords.forEach(selectedRecord -> {
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setId(selectedRecord.getName());
+ mqeValue.setEmptyValue(false);
+
mqeValue.setDoubleValue(Double.parseDouble(selectedRecord.getValue()));
+ mqeValueList.add(mqeValue);
+ });
+ Metadata metadata = new Metadata();
+ metadata.setName(metricName);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.setValues(mqeValueList);
+ mqeValues.setMetric(metadata);
+ result.getResults().add(mqeValues);
+ result.setType(ExpressionResultType.SORTED_LIST);
+ }
+
+ private void queryRecords(String metricName, int topN, Order order,
ExpressionResult result) throws IOException {
+ List<Record> records;
+ // API `returnTypeOfMQE` not require entity and duration.
+ if (entity != null && duration != null && recordsQuery != null) {
+ RecordCondition recordCondition = new RecordCondition();
+ recordCondition.setName(metricName);
+ recordCondition.setTopN(topN);
+ recordCondition.setParentEntity(entity);
+ recordCondition.setOrder(order);
+ records = recordsQuery.readRecords(recordCondition, duration);
+ } else {
+ records = Collections.emptyList();
+ }
+ List<MQEValue> mqeValueList = new ArrayList<>(records.size());
+ records.forEach(record -> {
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setId(record.getName());
+ mqeValue.setEmptyValue(false);
+ mqeValue.setDoubleValue(Double.parseDouble(record.getValue()));
+ mqeValue.setTraceID(record.getRefId());
+ mqeValueList.add(mqeValue);
+ });
+ Metadata metadata = new Metadata();
+ metadata.setName(metricName);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.setValues(mqeValueList);
+ mqeValues.setMetric(metadata);
+ result.getResults().add(mqeValues);
+ result.setType(ExpressionResultType.RECORD_LIST);
+ }
+
+ private void queryMetrics(String metricName, ExpressionResult result)
throws IOException {
+ MetricsValues metricsValues;
+ List<PointOfTime> times;
+ if (entity != null && duration != null && metricsQuery != null) {
+ MetricsCondition metricsCondition = new MetricsCondition();
+ metricsCondition.setName(metricName);
+ metricsCondition.setEntity(entity);
+ metricsValues = metricsQuery.readMetricsValues(metricsCondition,
duration);
+ times = duration.assembleDurationPoints();
+ } else {
+ metricsValues = new MetricsValues();
+ times = Collections.emptyList();
+ }
+ 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())
+ .getMillis();
+ KVInt kvInt = metricsValues.getValues().getValues().get(i);
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setId(Long.toString(retTimestamp));
+ mqeValue.setEmptyValue(kvInt.isEmptyValue());
+ mqeValue.setDoubleValue(kvInt.getValue());
+ mqeValueList.add(mqeValue);
+ }
+ Metadata metadata = new Metadata();
+ metadata.setName(metricName);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.setValues(mqeValueList);
+ mqeValues.setMetric(metadata);
+ result.getResults().add(mqeValues);
+ result.setType(ExpressionResultType.TIME_SERIES_VALUES);
+ }
+
+ private void queryLabeledMetrics(String metricName,
+ List<String> queryLabelList,
+ List<String> relabelList,
+ ExpressionResult result) throws
IOException {
+ List<MetricsValues> metricsValuesList;
+ List<PointOfTime> times;
+ if (entity != null && duration != null && metricsQuery != null) {
+ MetricsCondition metricsCondition = new MetricsCondition();
+ metricsCondition.setName(metricName);
+ metricsCondition.setEntity(entity);
+ metricsValuesList = metricsQuery.readLabeledMetricsValues(
+ metricsCondition, queryLabelList, duration);
+ times = duration.assembleDurationPoints();
+ } else {
+ metricsValuesList = Collections.emptyList();
+ times = Collections.emptyList();
+ }
+ Map<String, String> relabelMap = new HashMap<>();
+ for (int i = 0; i < queryLabelList.size(); i++) {
+ if (relabelList.size() > i) {
+ relabelMap.put(queryLabelList.get(i), relabelList.get(i));
+ continue;
+ }
+ relabelMap.put(queryLabelList.get(i), queryLabelList.get(i));
+ }
+ 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())
+ .getMillis();
+ KVInt kvInt = metricsValues.getValues().getValues().get(i);
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setEmptyValue(kvInt.isEmptyValue());
+ mqeValue.setId(Long.toString(retTimestamp));
+ mqeValueList.add(mqeValue);
+ if (!kvInt.isEmptyValue()) {
+ mqeValue.setDoubleValue(kvInt.getValue());
+ }
+ }
+
+ Metadata metadata = new Metadata();
+ metadata.setName(metricName);
+ KeyValue labelValue = new KeyValue("label",
relabelMap.getOrDefault(
+ metricsValues.getLabel(),
+ metricsValues.getLabel()
+ ));
+ metadata.getLabels().add(labelValue);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.setValues(mqeValueList);
+ mqeValues.setMetric(metadata);
+ result.getResults().add(mqeValues);
+ });
+ result.setType(ExpressionResultType.TIME_SERIES_VALUES);
+ }
+}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java
similarity index 55%
copy from
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
copy to
oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java
index e85ffe52ba..ace094e759 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/SelectedRecord.java
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/IllegalExpressionException.java
@@ -16,31 +16,10 @@
*
*/
-package org.apache.skywalking.oap.server.core.query.type;
+package org.apache.skywalking.oap.query.graphql.mqe.rt.exception;
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * SelectedRecord is an abtract data element, including id, name, value and a
reference id.
- */
-@Setter
-@Getter
-public class SelectedRecord {
- /**
- * Literal string name for visualization
- */
- private String name;
- /**
- * ID represents the owner of this entity.
- */
- private String id;
- /**
- * Usually an integer value as this is metrics.
- */
- private String value;
- /**
- * Have value, Only if the record has related trace id. UI should show
this as an attached value.
- */
- private String refId;
+public class IllegalExpressionException extends Exception {
+ public IllegalExpressionException(String message) {
+ super(message);
+ }
}
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/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java
similarity index 91%
copy from
oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java
copy to
oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java
index 9bab42e86f..1213458727 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/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/exception/ParseErrorListener.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.oap.query.promql.rt.exception;
+package org.apache.skywalking.oap.query.graphql.mqe.rt.exception;
import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.BaseErrorListener;
@@ -24,8 +24,6 @@ import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
public class ParseErrorListener extends BaseErrorListener {
- public static final ParseErrorListener INSTANCE = new ParseErrorListener();
-
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol,
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java
new file mode 100644
index 0000000000..8d9088ae70
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/AggregationOp.java
@@ -0,0 +1,111 @@
+/*
+ * 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.oap.query.graphql.mqe.rt.operation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.function.Function;
+import java.util.stream.DoubleStream;
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import
org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
+
+public class AggregationOp {
+ public static ExpressionResult doAggregationOp(ExpressionResult result,
+ int opType) throws
IllegalExpressionException {
+ switch (opType) {
+ case MQEParser.AVG:
+ return aggregateResult(result, mqeValues ->
mqeValues.getValues()
+ .stream()
+
.filter(mqeValue -> !mqeValue.isEmptyValue())
+
.flatMapToDouble(mqeValue -> DoubleStream.of(
+
mqeValue.getDoubleValue()))
+
.average());
+ case MQEParser.COUNT:
+ return aggregateResult(result, mqeValues -> OptionalDouble.of(
+ mqeValues.getValues().stream().filter(mqeValue ->
!mqeValue.isEmptyValue()).count()));
+ case MQEParser.MAX:
+ return selectResult(result, mqeValues -> mqeValues.getValues()
+ .stream()
+
.filter(mqeValue -> !mqeValue.isEmptyValue())
+
.max(Comparator.comparingDouble(
+
MQEValue::getDoubleValue)));
+ case MQEParser.MIN:
+ return selectResult(result, mqeValues -> mqeValues.getValues()
+ .stream()
+
.filter(mqeValue -> !mqeValue.isEmptyValue())
+
.min(Comparator.comparingDouble(
+
MQEValue::getDoubleValue)));
+ case MQEParser.SUM:
+ return aggregateResult(result, mqeValues ->
OptionalDouble.of(mqeValues.getValues()
+
.stream()
+
.filter(
+
mqeValue -> !mqeValue.isEmptyValue())
+
.flatMapToDouble(
+
mqeValue -> DoubleStream.of(
+
mqeValue.getDoubleValue()))
+
.sum()));
+ default:
+ throw new IllegalExpressionException("Unsupported aggregation
operation.");
+ }
+ }
+
+ private static ExpressionResult aggregateResult(ExpressionResult result,
+ Function<MQEValues,
OptionalDouble> aggregator) {
+ for (MQEValues resultValues : result.getResults()) {
+ OptionalDouble resultValue = aggregator.apply(resultValues);
+ if (resultValue.isPresent()) {
+ List<MQEValue> mqeValueList = new ArrayList<>(1);
+ //no id
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setEmptyValue(false);
+ mqeValue.setDoubleValue(resultValue.getAsDouble());
+ mqeValueList.add(mqeValue);
+ resultValues.setValues(mqeValueList);
+ } else {
+ resultValues.setValues(Collections.emptyList());
+ }
+ }
+ result.setType(ExpressionResultType.SINGLE_VALUE);
+ return result;
+ }
+
+ private static ExpressionResult selectResult(ExpressionResult result,
+ Function<MQEValues,
Optional<MQEValue>> aggregator) {
+ for (MQEValues resultValues : result.getResults()) {
+ Optional<MQEValue> resultValue = aggregator.apply(resultValues);
+ if (resultValue.isPresent()) {
+ List<MQEValue> mqeValueList = new ArrayList<>(1);
+ mqeValueList.add(resultValue.get());
+ resultValues.setValues(mqeValueList);
+ } else {
+ resultValues.setValues(Collections.emptyList());
+ }
+ }
+ result.setType(ExpressionResultType.SINGLE_VALUE);
+ return result;
+ }
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java
new file mode 100644
index 0000000000..a839a3f255
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/BinaryOp.java
@@ -0,0 +1,131 @@
+/*
+ * 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.oap.query.graphql.mqe.rt.operation;
+
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import
org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValue;
+import org.apache.skywalking.oap.query.graphql.type.mql.MQEValues;
+
+public class BinaryOp {
+ public static ExpressionResult doBinaryOp(ExpressionResult left,
+ ExpressionResult right,
+ int opType) throws
IllegalExpressionException {
+ if (left.getType() == ExpressionResultType.SINGLE_VALUE &&
right.getType() == ExpressionResultType.SINGLE_VALUE) {
+ double scalarLeft =
left.getResults().get(0).getValues().get(0).getDoubleValue();
+ double scalarRight =
right.getResults().get(0).getValues().get(0).getDoubleValue();
+ double value = scalarBinaryOp(scalarLeft, scalarRight, opType);
+ ExpressionResult result = new ExpressionResult();
+ MQEValue mqeValue = new MQEValue();
+ mqeValue.setDoubleValue(value);
+ mqeValue.setEmptyValue(false);
+ MQEValues mqeValues = new MQEValues();
+ mqeValues.getValues().add(mqeValue);
+ result.getResults().add(mqeValues);
+ result.setType(ExpressionResultType.SINGLE_VALUE);
+ return result;
+ } else if ((left.getType() == ExpressionResultType.TIME_SERIES_VALUES
||
+ left.getType() == ExpressionResultType.SORTED_LIST ||
+ left.getType() == ExpressionResultType.RECORD_LIST)
+ && right.getType() == ExpressionResultType.SINGLE_VALUE) {
+ return many2OneBinaryOp(left, right, opType);
+ } else if (left.getType() == ExpressionResultType.SINGLE_VALUE &&
+ (right.getType() == ExpressionResultType.TIME_SERIES_VALUES ||
+ right.getType() == ExpressionResultType.SORTED_LIST ||
+ right.getType() == ExpressionResultType.RECORD_LIST)) {
+ return many2OneBinaryOp(right, left, opType);
+ } else if (left.getType() == ExpressionResultType.TIME_SERIES_VALUES
&& right.getType() == ExpressionResultType.TIME_SERIES_VALUES) {
+ return seriesBinaryOp(right, left, opType);
+ }
+
+ throw new IllegalExpressionException("Unsupported binary operation.");
+ }
+
+ //scalar with scalar
+ private static double scalarBinaryOp(double leftValue, double rightValue,
int opType) {
+ double calculatedResult = 0;
+ switch (opType) {
+ case MQEParser.ADD:
+ calculatedResult = leftValue + rightValue;
+ break;
+ case MQEParser.SUB:
+ calculatedResult = leftValue - rightValue;
+ break;
+ case MQEParser.MUL:
+ calculatedResult = leftValue * rightValue;
+ break;
+ case MQEParser.DIV:
+ calculatedResult = leftValue / rightValue;
+ break;
+ case MQEParser.MOD:
+ calculatedResult = leftValue % rightValue;
+ break;
+ }
+ return calculatedResult;
+ }
+
+ //series with series
+ private static ExpressionResult seriesBinaryOp(ExpressionResult seriesLeft,
+ ExpressionResult seriesRight,
+ int opType) throws
IllegalExpressionException {
+ if (seriesLeft.getResults().size() == 1 &&
seriesRight.getResults().size() == 1) {
+ MQEValues mqeValuesL = seriesLeft.getResults().get(0);
+ MQEValues mqeValuesR = seriesRight.getResults().get(0);
+ for (int i = 0; i < mqeValuesL.getValues().size(); i++) {
+ //clean metric info
+ mqeValuesL.setMetric(null);
+ MQEValue valueL = mqeValuesL.getValues().get(i);
+ MQEValue valueR = mqeValuesR.getValues().get(i);
+ if (valueL.isEmptyValue() || valueR.isEmptyValue()) {
+ valueL.setEmptyValue(true);
+ valueL.setDoubleValue(0);
+ continue;
+ }
+ //time should be mapped
+ double newValue = scalarBinaryOp(valueL.getDoubleValue(),
valueR.getDoubleValue(), opType);
+ mqeValuesL.getValues().get(i).setDoubleValue(newValue);
+ }
+
+ } else {
+ throw new IllegalExpressionException("Binary operation don't
support labeled metrics.");
+ }
+
+ return seriesLeft;
+ }
+
+ //series or list with scalar
+ private static ExpressionResult many2OneBinaryOp(ExpressionResult
manyResult, ExpressionResult singleResult, int opType) {
+ manyResult.getResults().forEach(mqeValues -> {
+ mqeValues.getValues().forEach(mqeValue -> {
+ if (!mqeValue.isEmptyValue()) {
+ double newValue = scalarBinaryOp(
+ mqeValue.getDoubleValue(), singleResult.getResults()
+ .get(0)
+ .getValues()
+ .get(0)
+
.getDoubleValue(), opType);
+ mqeValue.setDoubleValue(newValue);
+ }
+ });
+ });
+ return manyResult;
+ }
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.java
new file mode 100644
index 0000000000..0b39fa73a8
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/mqe/rt/operation/FunctionOp.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.oap.query.graphql.mqe.rt.operation;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.function.Function;
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+import
org.apache.skywalking.oap.query.graphql.mqe.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
+
+public class FunctionOp {
+ public static ExpressionResult doFunction0Op(ExpressionResult expResult,
+ int opType) throws
IllegalExpressionException {
+ switch (opType) {
+ case MQEParser.ABS:
+ return FunctionOp.transResult(expResult, Math::abs);
+ case MQEParser.CEIL:
+ return FunctionOp.transResult(expResult, Math::ceil);
+ case MQEParser.FLOOR:
+ return FunctionOp.transResult(expResult, Math::floor);
+ }
+
+ throw new IllegalExpressionException("Unsupported function.");
+ }
+
+ public static ExpressionResult doFunction1Op(ExpressionResult expResult,
+ int opType,
+ MQEParser.ParameterContext
params) throws IllegalExpressionException {
+ switch (opType) {
+ case MQEParser.ROUND:
+ return FunctionOp.transResult(expResult, aDouble -> {
+ BigDecimal bd = BigDecimal.valueOf(aDouble);
+ return
bd.setScale(Integer.parseInt(params.INTEGER().getText()),
RoundingMode.HALF_UP).doubleValue();
+ });
+ }
+
+ throw new IllegalExpressionException("Unsupported function.");
+ }
+
+ private static ExpressionResult transResult(ExpressionResult expResult,
Function<Double, Double> calculator) {
+ expResult.getResults().forEach(resultValues -> {
+ resultValues.getValues().forEach(mqeValue -> {
+ if (!mqeValue.isEmptyValue()) {
+ double newValue =
calculator.apply(mqeValue.getDoubleValue());
+ mqeValue.setDoubleValue(newValue);
+ }
+ });
+ });
+ return expResult;
+ }
+
+ public static void main(String[] args) {
+ System.out.printf(String.valueOf(Math.floor(1.1112)));
+ }
+}
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
new file mode 100644
index 0000000000..4f8735575d
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsExpressionQuery.java
@@ -0,0 +1,98 @@
+/*
+ * 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.oap.query.graphql.resolver;
+
+import graphql.kickstart.tools.GraphQLQueryResolver;
+import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import java.text.DecimalFormat;
+import java.util.Collections;
+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.graphql.mqe.rt.MQEVisitor;
+import
org.apache.skywalking.oap.query.graphql.mqe.rt.exception.ParseErrorListener;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResult;
+import org.apache.skywalking.oap.query.graphql.type.mql.ExpressionResultType;
+import org.apache.skywalking.oap.server.core.query.input.Entity;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.mqe.rt.grammar.MQELexer;
+import org.apache.skywalking.mqe.rt.grammar.MQEParser;
+
+public class MetricsExpressionQuery implements GraphQLQueryResolver {
+ private final MetricsQuery metricsQuery;
+ private final RecordsQuery recordsQuery;
+ private final DecimalFormat valueFormat = new DecimalFormat();
+
+ public MetricsExpressionQuery(ModuleManager moduleManager) {
+ this.metricsQuery = new MetricsQuery(moduleManager);
+ this.recordsQuery = new RecordsQuery(moduleManager);
+ this.valueFormat.setGroupingUsed(false);
+ }
+
+ public ExpressionResult returnTypeOfMQE(String expression) {
+ 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();
+ } catch (ParseCancellationException e) {
+ ExpressionResult errorResult = new ExpressionResult();
+ errorResult.setType(ExpressionResultType.UNKNOWN);
+ errorResult.setError(e.getMessage());
+ return errorResult;
+ }
+ MQEVisitor visitor = new MQEVisitor();
+ ExpressionResult parseResult = visitor.visit(tree);
+ parseResult.setResults(Collections.emptyList());
+ return parseResult;
+ }
+
+ public ExpressionResult execExpression(String expression, Entity entity,
Duration duration) {
+ 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();
+ } catch (ParseCancellationException e) {
+ ExpressionResult errorResult = new ExpressionResult();
+ errorResult.setType(ExpressionResultType.UNKNOWN);
+ errorResult.setError(e.getMessage());
+ return errorResult;
+ }
+ MQEVisitor visitor = new MQEVisitor(metricsQuery, recordsQuery,
entity, duration);
+ ExpressionResult parseResult = visitor.visit(tree);
+
+ parseResult.getResults().forEach(mqeValues -> {
+ mqeValues.getValues().forEach(mqeValue -> {
+ if (!mqeValue.isEmptyValue()) {
+
mqeValue.setValue(valueFormat.format(mqeValue.getDoubleValue()));
+ }
+ });
+ });
+
+ return parseResult;
+ }
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java
new file mode 100644
index 0000000000..ea467bc017
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResult.java
@@ -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.
+ */
+
+package org.apache.skywalking.oap.query.graphql.type.mql;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+
+@Data
+public class ExpressionResult {
+ private ExpressionResultType type = ExpressionResultType.UNKNOWN;
+ private List<MQEValues> results = new ArrayList<>();
+ private String error;
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java
new file mode 100644
index 0000000000..5820e04c62
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/ExpressionResultType.java
@@ -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.
+ */
+
+package org.apache.skywalking.oap.query.graphql.type.mql;
+
+public enum ExpressionResultType {
+
+ /**
+ * Can't resolve the type of the given expression.
+ */
+ UNKNOWN,
+ /**
+ * A single value
+ */
+ SINGLE_VALUE,
+ /**
+ * A collection of time-series values.
+ * The value could have labels or not.
+ */
+ TIME_SERIES_VALUES,
+ /**
+ * A collection of aggregated values through metric sort function.
+ */
+ SORTED_LIST,
+ /**
+ * A collection of sampled records.
+ * When the original metric type is sampled records.
+ */
+ RECORD_LIST
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java
new file mode 100644
index 0000000000..d6a7b548fd
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValue.java
@@ -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.
+ */
+
+package org.apache.skywalking.oap.query.graphql.type.mql;
+
+import lombok.Data;
+
+@Data
+public class MQEValue {
+ private String id;
+ private String value;
+ private String traceID;
+
+ private double doubleValue;
+ private boolean isEmptyValue;
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java
new file mode 100644
index 0000000000..7f45f41f5f
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/MQEValues.java
@@ -0,0 +1,34 @@
+/*
+ * 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.oap.query.graphql.type.mql;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+
+public class MQEValues {
+ @Getter
+ @Setter
+ private Metadata metric;
+ @Getter
+ @Setter
+ private List<MQEValue> values = new ArrayList<>();
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java
new file mode 100644
index 0000000000..f63923166f
--- /dev/null
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/type/mql/Metadata.java
@@ -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.
+ */
+
+package org.apache.skywalking.oap.query.graphql.type.mql;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.query.type.KeyValue;
+
+public class Metadata {
+ @Getter
+ @Setter
+ private String name;
+ @Getter
+ @Setter
+ private List<KeyValue> labels = new ArrayList<>();
+}
diff --git
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
index a711fd9d0f..957b0120af 160000
---
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
+++
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
@@ -1 +1 @@
-Subproject commit a711fd9d0f94a67a933eacf30a12976c6219c416
+Subproject commit 957b0120af74dde1b2fca2449914739bd97c435f
diff --git a/skywalking-ui b/skywalking-ui
index 986fe8b90e..dc22f8da6e 160000
--- a/skywalking-ui
+++ b/skywalking-ui
@@ -1 +1 @@
-Subproject commit 986fe8b90e238e889ab93ae92dc5b7482bf122f5
+Subproject commit dc22f8da6e5fe60fa0c4e90dc4e3f37361384b03