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


Reply via email to