Repository: apache/skywalking (language: python)

Issue:
[Feature] investigate the possibility for supporting PromQL
### Search before asking

- [X] I had searched in the [issues](https://github.com/apache/skywalking/issues?q=is%3Aissue) and found no similar feature requirement.


### Description

In the past two years, we supported Prometheus through OpenTelemetry(OC and OLTP) for ingest. We have several asks about how about query stage.
It is early to say we could or should support it, but there is a chance we could investigate the possibility and meaning of PromQL.

Ref PromQL 2.37(LTS), https://prometheus.io/docs/prometheus/2.37/getting_started/

We would do more discussions in this thread to update the progress. 

**Notice, this has not been selected on the roadmap.**

### Use case

People who like Grafana, or other query platforms supporting PromQL, could read metrics from SkyWalking. 

If this gets verified and supported, we could do research for traces and logs for more query protocols.


### Related issues

_No response_

### Are you willing to submit a PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct)



Discussion / hints:
FYI @apache/skywalking-committers If any of you feel interests, you could follow this thread.
MAL core semantics are a variant of PromQL, so we have some experience with PromQL. I want to discuss the scope of PromQL to OAP:

  * Do we tend to use PromQL as an alternative to GraphQL-based APIs? Beside Topology and Trace querying, PromQL might be in place of most scenarios. But we can extend PromQL to support them if we want to go "all-in" PromQL. 
  * Do we replace MAL and LAL with PromQL since they derive from the latter?
  * Furthermore, will OAL get involved?
I can't see the point to do the replacement, because that would impact the whole query.
Also, SkyWalking would keep in pre-calculation as recommended. The main point of this is exposing the aggregated query in another channel, nothing more.
So, MAL, OAL, GraphQL APIs are kept for good reasons.
The current multiple dimensions query would be the fundamental query to provide time-series data, this would not even impact the storage level. The whole PromeQL implementation is on top of GraphQL. @hanahmily 
I reviewed the PromQL. I think besides the basic grammar, we could choose these four APIs as MVP
1. Metadata Query API, https://prometheus.io/docs/prometheus/2.37/querying/api/#querying-metadata. Metadata has a specific meaning for us. We could expose our metadata metric names(e.g. service_traffic).
   - listMetrics could be considered a kind of metadata if possible(TBD)
2. Metrics value Query API,  https://prometheus.io/docs/prometheus/2.37/querying/api/#expression-queries. Metrics as time-series values could be queried and returned in the expected format.
   - When the `range` is missed, we could pick the latest 30 mins
   - We need to recognize and pick dimensions automatically, to avoid `too many data points` error and overload
   - There should be a complete label set to define our scope in order to get metrics correctly. I didn't read Grafana+PromQL Datasource carefully yet, but I hope we have a chance to provide topology, at least topology line metrics.
3. Alerts API, https://prometheus.io/docs/prometheus/2.37/querying/api/#alerts. This could directly query our alarms with some pre-defined labels(e.g. rule_name)
4. Build Info, https://prometheus.io/docs/prometheus/2.37/querying/api/#build-information. This is our version service.

With all the implementation, we need to define our details, mostly about specific labels. Complete docs should be added under `APIs/Query APIs/PromQL`
<img width="247" alt="image" src="https://user-images.githubusercontent.com/5441976/209427655-76b5969a-27c9-4f3c-ba4f-8f082c5879c3.png">
 
**Notice, we should explicitly declare at the top of the new PromQL API doc. The APIs are built on top of GraphQL APIs, and only provide a feature subset of GraphQL APIs**.

@wankai123 Please add anything you found on your side. 
I just removed `TBD` label. Sync from @wankai123 we at least could have metadata and metric query support.
This should be possible to continue on implement details.

Fix patch:
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 128748ef9c1d..eb8fdd4732b5 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -118,7 +118,7 @@ dependency:
       version: 2.1.212
       license: MPL-2.0
     - name: org.antlr:antlr4-runtime
-      version: 4.9.2
+      version: 4.11.1
       license: BSD-3-Clause
     - name: com.google.flatbuffers:flatbuffers-java
       version: 1.12.0
diff --git a/dist-material/release-docs/LICENSE b/dist-material/release-docs/LICENSE
index 3509c5a7424b..9e553204bd42 100644
--- a/dist-material/release-docs/LICENSE
+++ b/dist-material/release-docs/LICENSE
@@ -368,7 +368,7 @@ The text of each license is also included in licenses/LICENSE-[project].txt.
     https://npmjs.com/package/d3-ease/v/3.0.1 3.0.1 BSD-3-Clause
     https://npmjs.com/package/d3-tip/node_modules/d3-selection/v/1.4.2 1.4.2 BSD-3-Clause
     https://npmjs.com/package/normalize-wheel-es/v/1.2.0 1.2.0 BSD-3-Clause
-    https://mvnrepository.com/artifact/org.antlr/antlr4-runtime/4.9.2 BSD-3-Clause
+    https://mvnrepository.com/artifact/org.antlr/antlr4-runtime/4.11.1 BSD-3-Clause
     https://npmjs.com/package/rw/v/1.3.3 1.3.3 BSD-3-Clause
     https://npmjs.com/package/source-map/v/0.6.1 0.6.1 BSD-3-Clause
     https://npmjs.com/package/source-map-js/v/1.0.2 1.0.2 BSD-3-Clause
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 4c40596c0a1f..e8bdfbd0179a 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -100,6 +100,8 @@
 * Support Amazon Simple Storage Service (Amazon S3) metrics monitoring
 * Support process Sum metrics with AGGREGATION_TEMPORALITY_DELTA case
 * Support Amazon DynamoDB monitoring.
+* Support prometheus HTTP API and promQL.
+* `Scope` in the Entity of Metrics query v1 protocol is not required and automatical correction. The scope is determined based on the metric itself.
 
 #### UI
 
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
index 47b59ab72101..2b4c16d19458 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
@@ -63,7 +63,7 @@ public DatabaseSlowStatement toDatabaseSlowStatement() {
         DatabaseSlowStatement dbSlowStat = new DatabaseSlowStatement();
         dbSlowStat.setId(id);
         dbSlowStat.setTraceId(traceId);
-        dbSlowStat.setDatabaseServiceId(IDManager.ServiceID.buildId(serviceName, false));
+        dbSlowStat.setDatabaseServiceId(IDManager.ServiceID.buildId(serviceName, layer.isNormal()));
         dbSlowStat.setStatement(statement);
         dbSlowStat.setLatency(latency);
         dbSlowStat.setTimeBucket(timeBucket);
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
index d74c98ec3ac0..ac2cdaf6f7be 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
@@ -32,10 +32,10 @@ public enum DurationUtils {
 
     private static final int MAX_TIME_RANGE = 500;
 
-    private static final DateTimeFormatter YYYY_MM_DD = DateTimeFormat.forPattern("yyyy-MM-dd");
-    private static final DateTimeFormatter YYYY_MM_DD_HH = DateTimeFormat.forPattern("yyyy-MM-dd HH");
-    private static final DateTimeFormatter YYYY_MM_DD_HHMM = DateTimeFormat.forPattern("yyyy-MM-dd HHmm");
-    private static final DateTimeFormatter YYYY_MM_DD_HHMMSS = DateTimeFormat.forPattern("yyyy-MM-dd HHmmss");
+    public static final DateTimeFormatter YYYY_MM_DD = DateTimeFormat.forPattern("yyyy-MM-dd");
+    public static final DateTimeFormatter YYYY_MM_DD_HH = DateTimeFormat.forPattern("yyyy-MM-dd HH");
+    public static final DateTimeFormatter YYYY_MM_DD_HHMM = DateTimeFormat.forPattern("yyyy-MM-dd HHmm");
+    public static final DateTimeFormatter YYYY_MM_DD_HHMMSS = DateTimeFormat.forPattern("yyyy-MM-dd HHmmss");
 
     private static final DateTimeFormatter YYYYMMDD = DateTimeFormat.forPattern("yyyyMMdd");
     private static final DateTimeFormatter YYYYMMDDHH = DateTimeFormat.forPattern("yyyyMMddHH");
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsMetadataQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsMetadataQueryService.java
index 8ea771796769..263093a6baec 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsMetadataQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsMetadataQueryService.java
@@ -31,7 +31,7 @@
  * MetricsMetadataQueryService provides the metadata of metrics to other modules.
  */
 public class MetricsMetadataQueryService implements Service {
-    public MetricsType typeOfMetrics(String metricsName) {
+    public static MetricsType typeOfMetrics(String metricsName) {
         final Optional<ValueColumnMetadata.ValueColumn> valueColumn
             = ValueColumnMetadata.INSTANCE.readValueColumnDefinition(metricsName);
         if (valueColumn.isPresent()) {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/RecordQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/RecordQueryService.java
index ed8bf65e85b6..b10e8ec86958 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/RecordQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/RecordQueryService.java
@@ -51,4 +51,4 @@ public List<Record> readRecords(RecordCondition condition, Duration duration) th
         return getRecordsQueryDAO().readRecords(
             condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), duration);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
index 871931fb1c72..1e615e27f863 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
@@ -19,7 +19,6 @@
 package org.apache.skywalking.oap.server.core.query.input;
 
 import java.util.Objects;
-import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
@@ -31,7 +30,7 @@
  * @since 8.0.0
  */
 @Setter
-@Getter(AccessLevel.PRIVATE)
+@Getter
 public class Entity {
     /**
      * <pre>
@@ -42,6 +41,11 @@ public class Entity {
      *    estServiceName/destServiceInstanceName/destEndpointName is/are destination(s)
      *    set necessary names of sources and destinations.
      * </pre>
+     *
+     * @see MetricsCondition
+     * @see RecordCondition
+     * @see TopNCondition
+     * @since 9.4.0 Scope could be sensed automatically through query condition's metric name.
      */
     private Scope scope;
 
@@ -147,12 +151,16 @@ public String buildId() {
             case ProcessRelation:
                 return IDManager.ProcessID.buildRelationId(
                     new IDManager.ProcessID.ProcessRelationDefine(
-                        IDManager.ProcessID.buildId(IDManager.ServiceInstanceID.buildId(
-                            IDManager.ServiceID.buildId(serviceName, normal), serviceInstanceName),
-                            processName),
-                        IDManager.ProcessID.buildId(IDManager.ServiceInstanceID.buildId(
-                            IDManager.ServiceID.buildId(destServiceName, destNormal), destServiceInstanceName),
-                            destProcessName)
+                        IDManager.ProcessID.buildId(
+                            IDManager.ServiceInstanceID.buildId(
+                                IDManager.ServiceID.buildId(serviceName, normal), serviceInstanceName),
+                            processName
+                        ),
+                        IDManager.ProcessID.buildId(
+                            IDManager.ServiceInstanceID.buildId(
+                                IDManager.ServiceID.buildId(destServiceName, destNormal), destServiceInstanceName),
+                            destProcessName
+                        )
                     )
                 );
             default:
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
index 11c1232abe28..53a2a0abec8f 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
@@ -20,6 +20,9 @@
 
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.skywalking.oap.server.core.query.MetricsMetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.enumeration.MetricsType;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 
 /**
  * @since 8.0.0
@@ -35,4 +38,16 @@ public class MetricsCondition {
      * See {@link Entity}
      */
     private Entity entity;
+
+    /**
+     * Sense Scope through metric name.
+     * @return false if not a valid metric name.
+     */
+    public boolean senseScope() {
+        if (MetricsType.UNKNOWN.equals(MetricsMetadataQueryService.typeOfMetrics(name))) {
+            return false;
+        }
+        entity.setScope(ValueColumnMetadata.INSTANCE.getScope(name));
+        return true;
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/RecordCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/RecordCondition.java
index 1b9aa08cdf81..45a71b193963 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/RecordCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/RecordCondition.java
@@ -20,8 +20,11 @@
 
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.skywalking.oap.server.core.query.MetricsMetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.enumeration.MetricsType;
 import org.apache.skywalking.oap.server.core.query.enumeration.Order;
 import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
 /**
@@ -37,8 +40,7 @@ public class RecordCondition {
      */
     private String name;
     /**
-     * Follow {@link Entity} definition description.
-     * The owner of the sampled records.
+     * Follow {@link Entity} definition description. The owner of the sampled records.
      */
     private Entity parentEntity;
     private int topN;
@@ -59,4 +61,16 @@ public RecordCondition(TopNCondition condition) {
         this.topN = condition.getTopN();
         this.order = condition.getOrder();
     }
-}
\ No newline at end of file
+
+    /**
+     * Sense Scope through metric name.
+     * @return false if not a valid metric name.
+     */
+    public boolean senseScope() {
+        if (MetricsType.UNKNOWN.equals(MetricsMetadataQueryService.typeOfMetrics(name))) {
+            return false;
+        }
+        parentEntity.setScope(ValueColumnMetadata.INSTANCE.getScope(name));
+        return true;
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/TopNCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/TopNCondition.java
index 25d1d7874eac..38c250905b39 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/TopNCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/TopNCondition.java
@@ -20,8 +20,11 @@
 
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.skywalking.oap.server.core.query.MetricsMetadataQueryService;
+import org.apache.skywalking.oap.server.core.query.enumeration.MetricsType;
 import org.apache.skywalking.oap.server.core.query.enumeration.Order;
 import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 
 /**
  * Top N query condition.
@@ -52,4 +55,16 @@ public class TopNCondition {
     private Scope scope;
     private int topN;
     private Order order;
+
+    /**
+     * Sense Scope through metric name.
+     * @return false if not a valid metric name.
+     */
+    public boolean senseScope() {
+        if (MetricsType.UNKNOWN.equals(MetricsMetadataQueryService.typeOfMetrics(name))) {
+            return false;
+        }
+        scope = ValueColumnMetadata.INSTANCE.getScope(name);
+        return true;
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
index 1d51297e5516..85531062c708 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
@@ -21,8 +21,10 @@
 import io.vavr.collection.Stream;
 import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
 
 public class IntValues {
+    @Getter
     private List<KVInt> values = new ArrayList<>();
 
     public void addKVInt(KVInt e) {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
index d9c0bde80910..a33bfb53a0fb 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
@@ -23,6 +23,7 @@
 import java.util.Optional;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
 import org.apache.skywalking.oap.server.core.query.sql.Function;
 
 /**
@@ -84,6 +85,10 @@ public Map<String, ValueColumn> getAllMetadata() {
         return mapping;
     }
 
+    public Scope getScope(String metricsName) {
+        return Scope.Finder.valueOf(findColumn(metricsName).scopeId);
+    }
+
     private ValueColumn findColumn(String metricsName) {
         ValueColumn column = mapping.get(metricsName);
         if (column == null) {
diff --git a/oap-server/server-query-plugin/pom.xml b/oap-server/server-query-plugin/pom.xml
index 9d7c764231a1..a4060dceb86c 100644
--- a/oap-server/server-query-plugin/pom.xml
+++ b/oap-server/server-query-plugin/pom.xml
@@ -30,5 +30,6 @@
     <modules>
         <module>query-graphql-plugin</module>
         <module>zipkin-query-plugin</module>
+        <module>promql-plugin</module>
     </modules>
 </project>
diff --git a/oap-server/server-query-plugin/promql-plugin/pom.xml b/oap-server/server-query-plugin/promql-plugin/pom.xml
new file mode 100644
index 000000000000..fac4a71445c3
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<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>server-query-plugin</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>9.4.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>promql-plugin</artifactId>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>server-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>query-graphql-plugin</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <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/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4 b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
new file mode 100644
index 000000000000..087f9b213a58
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLLexer.g4
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+lexer grammar PromQLLexer;
+
+// Keywords
+BOOL options { caseInsensitive=true; }: 'bool';
+
+// 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:          '>';
+
+// Literals
+NUMBER: Digit+ (DOT Digit+)?;
+DURATION: Digit+ ('s' | 'm' | 'h' | 'd' | 'w' | 'y');
+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/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4 b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
new file mode 100644
index 000000000000..ae29a801e0e0
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/antlr4/org/apache/skywalking/promql/rt/grammar/PromQLParser.g4
@@ -0,0 +1,50 @@
+/*
+ * 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 PromQLParser;
+
+options { tokenVocab = PromQLLexer; }
+
+root: expression;
+
+expression
+    : expressionNode                   # exprNode
+    | expression mulDivMod expression  # mulDivModOp
+    | expression addSub expression     # addSubOp
+    | expression compare expression    # compareOp
+    ;
+
+expressionNode:  metricInstant| metricRange| numberLiteral| badRange;
+
+addSub:          ADD | SUB ;
+mulDivMod:       MUL | DIV | MOD;
+compare:        (DEQ | NEQ | LTE | LT | GTE | GT) BOOL?;
+
+metricName:      NAME_STRING;
+metricInstant:   metricName | metricName L_BRACE labelList R_BRACE;
+metricRange:     metricInstant L_BRACKET DURATION R_BRACKET;
+
+labelName:       NAME_STRING;
+labelValue:      VALUE_STRING;
+label:           labelName EQ labelValue;
+labelList:       label (COMMA label)*;
+
+numberLiteral:   NUMBER;
+
+badRange:        NUMBER L_BRACKET DURATION R_BRACKET;
+
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLConfig.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLConfig.java
new file mode 100644
index 000000000000..244cb50b5d4e
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLConfig.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.promql;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+
+@Setter
+@Getter
+public class PromQLConfig extends ModuleConfig {
+    private String restHost;
+    private int restPort;
+    private String restContextPath;
+    private int restMaxThreads = 200;
+    private long restIdleTimeOut = 30000;
+    private int restAcceptQueueSize = 0;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLModule.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLModule.java
new file mode 100644
index 000000000000..c8d35ef13331
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLModule.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.promql;
+
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+
+public class PromQLModule extends ModuleDefine {
+    public static final String NAME = "promql";
+
+    public PromQLModule() {
+        super(NAME);
+    }
+
+    @Override
+    public Class[] services() {
+        return new Class[0];
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLProvider.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLProvider.java
new file mode 100644
index 000000000000..ff04aa8cc1c5
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/PromQLProvider.java
@@ -0,0 +1,99 @@
+/*
+ * 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.promql;
+
+import com.linecorp.armeria.common.HttpMethod;
+import java.util.Arrays;
+import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServer;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
+import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
+
+public class PromQLProvider extends ModuleProvider {
+    public static final String NAME = "default";
+    private PromQLConfig config;
+    private HTTPServer httpServer;
+
+    @Override
+    public String name() {
+        return NAME;
+    }
+
+    @Override
+    public Class<? extends ModuleDefine> module() {
+        return PromQLModule.class;
+    }
+
+    @Override
+    public ConfigCreator newConfigCreator() {
+        return new ConfigCreator<PromQLConfig>() {
+            @Override
+            public Class type() {
+                return PromQLConfig.class;
+            }
+
+            @Override
+            public void onInitialized(final PromQLConfig initialized) {
+                config = initialized;
+            }
+        };
+    }
+
+    @Override
+    public void prepare() throws ServiceNotProvidedException {
+
+    }
+
+    @Override
+    public void start() throws ServiceNotProvidedException, ModuleStartException {
+        HTTPServerConfig httpServerConfig = HTTPServerConfig.builder()
+                                                            .host(config.getRestHost())
+                                                            .port(config.getRestPort())
+                                                            .contextPath(config.getRestContextPath())
+                                                            .idleTimeOut(config.getRestIdleTimeOut())
+                                                            .maxThreads(config.getRestMaxThreads())
+                                                            .acceptQueueSize(config.getRestAcceptQueueSize())
+                                                            .build();
+
+        httpServer = new HTTPServer(httpServerConfig);
+        httpServer.initialize();
+        httpServer.addHandler(
+            new PromQLApiHandler(getManager()),
+            Arrays.asList(HttpMethod.POST, HttpMethod.GET)
+        );
+    }
+
+    @Override
+    public void notifyAfterCompleted() {
+        httpServer.start();
+    }
+
+    @Override
+    public String[] requiredModules() {
+        return new String[] {
+            TelemetryModule.NAME,
+            CoreModule.NAME
+        };
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ErrorType.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ErrorType.java
new file mode 100644
index 000000000000..dc781635d537
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ErrorType.java
@@ -0,0 +1,42 @@
+/*
+ * 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.promql.entity;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum ErrorType {
+    BAD_DATA("bad_data"),
+    INTERNAL("internal");
+
+    final String value;
+
+    ErrorType(final String value) {
+        this.value = value;
+    }
+
+    @JsonValue
+    public String getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelName.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelName.java
new file mode 100644
index 000000000000..4e2e1c740c75
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelName.java
@@ -0,0 +1,80 @@
+/*
+ * 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.promql.entity;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+@JsonSerialize()
+public enum LabelName {
+    NAME("__name__"),
+    LAYER("layer"),
+    SCOPE("scope"),
+    SERVICE("service"),
+    SERVICE_INSTANCE("service_instance"),
+    ENDPOINT("endpoint"),
+    //For labeled value query
+    LABELS("labels"),
+    RELABELS("relabels"),
+    //For labeled value
+    LABEL("label"),
+    //For tonN and record
+    PARENT_SERVICE("parent_service"),
+    TOP_N("top_n"),
+    ORDER("order"),
+    RECORD("record"),
+    //For endpoint_traffic
+    LIMIT("limit"),
+    KEYWORD("keyword");
+
+    final String label;
+
+    private static final Map<String, LabelName> DICTIONARY = new HashMap<>();
+
+    static {
+        Arrays.stream(LabelName.values()).forEach(l -> {
+            DICTIONARY.put(l.label, l);
+        });
+    }
+
+    LabelName(final String label) {
+        this.label = label;
+    }
+
+    public static LabelName labelOf(String label) {
+        LabelName labelName = DICTIONARY.get(label);
+        if (labelName == null) {
+            throw new IllegalArgumentException("Unknown Label Name: " + label);
+        }
+        return labelName;
+    }
+
+    @JsonValue
+    public String getLabel() {
+        return this.label;
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelValuePair.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelValuePair.java
new file mode 100644
index 000000000000..c1f78c1f3fad
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/LabelValuePair.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.promql.entity;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+@EqualsAndHashCode
+public class LabelValuePair {
+    private final LabelName labelName;
+    private final String labelValue;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricData.java
new file mode 100644
index 000000000000..221ddbc93fda
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricData.java
@@ -0,0 +1,28 @@
+/*
+ * 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.promql.entity;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+@Data
+public class MetricData {
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private MetricInfo metric;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInfo.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInfo.java
new file mode 100644
index 000000000000..c934cde940b7
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.promql.entity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.oap.query.promql.entity.codec.MetricInfoSerializer;
+
+@Data
+@JsonSerialize(using = MetricInfoSerializer.class)
+@RequiredArgsConstructor
+@EqualsAndHashCode
+public class MetricInfo {
+    @JsonProperty("__name__")
+    private final String name;
+    private List<LabelValuePair> labels = new ArrayList<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInstantData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInstantData.java
new file mode 100644
index 000000000000..7bfdc07d41b0
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricInstantData.java
@@ -0,0 +1,28 @@
+/*
+ * 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.promql.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MetricInstantData extends MetricData {
+    private TimeValuePair value;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricMetadata.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricMetadata.java
new file mode 100644
index 000000000000..b19ee46cb4d5
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricMetadata.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.promql.entity;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.oap.query.promql.entity.response.MetricType;
+
+@Data
+@RequiredArgsConstructor
+public class MetricMetadata {
+    private final MetricType type;
+    private final String help;
+    private final String unit;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricRangeData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricRangeData.java
new file mode 100644
index 000000000000..eccfd550f63b
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/MetricRangeData.java
@@ -0,0 +1,27 @@
+/*
+ * 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.promql.entity;
+
+import java.util.List;
+import lombok.Data;
+
+@Data
+public class MetricRangeData extends MetricData {
+    private List<TimeValuePair> values;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResponseData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResponseData.java
new file mode 100644
index 000000000000..cbaa0ca39326
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResponseData.java
@@ -0,0 +1,27 @@
+/*
+ * 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.promql.entity;
+
+import lombok.Data;
+import org.apache.skywalking.oap.query.promql.entity.response.ResultType;
+
+@Data
+public class ResponseData {
+    private ResultType resultType;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResultStatus.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResultStatus.java
new file mode 100644
index 000000000000..dc42101c1d01
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/ResultStatus.java
@@ -0,0 +1,42 @@
+/*
+ * 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.promql.entity;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum ResultStatus {
+    SUCCESS("success"),
+    ERROR("error");
+
+    final String value;
+
+    ResultStatus(final String value) {
+        this.value = value;
+    }
+
+    @JsonValue
+    public String getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.java
new file mode 100644
index 000000000000..68e32065c2eb
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/TimeValuePair.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.promql.entity;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import lombok.Data;
+import org.apache.skywalking.oap.query.promql.entity.codec.TimeValuePairSerializer;
+
+@Data
+@JsonSerialize(using = TimeValuePairSerializer.class)
+public class TimeValuePair {
+    private final long time;
+    private final String value;
+
+    public TimeValuePair(final long time, String value) {
+        this.time = time;
+        this.value = value;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/MetricInfoSerializer.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/MetricInfoSerializer.java
new file mode 100644
index 000000000000..1644405844d0
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/MetricInfoSerializer.java
@@ -0,0 +1,44 @@
+/*
+ * 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.promql.entity.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
+
+public class MetricInfoSerializer extends JsonSerializer<MetricInfo> {
+    @Override
+    public void serialize(final MetricInfo value,
+                          final JsonGenerator gen,
+                          final SerializerProvider serializers) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+        gen.writeStartObject();
+        {
+            gen.writeStringField("__name__", value.getName());
+            for (LabelValuePair label: value.getLabels()) {
+                gen.writeStringField(label.getLabelName().getLabel(), label.getLabelValue());
+            }
+        }
+        gen.writeEndObject();
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/TimeValuePairSerializer.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/TimeValuePairSerializer.java
new file mode 100644
index 000000000000..7420b3ec74bb
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/codec/TimeValuePairSerializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.promql.entity.codec;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
+
+public class TimeValuePairSerializer extends JsonSerializer<TimeValuePair> {
+
+    @Override
+    public void serialize(final TimeValuePair value,
+                          final JsonGenerator gen,
+                          final SerializerProvider serializers) throws IOException {
+        gen.setRootValueSeparator(new SerializedString("\n"));
+        gen.writeStartArray();
+        {
+            gen.writeNumber(value.getTime());
+            gen.writeString(value.getValue());
+        }
+        gen.writeEndArray();
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ExprQueryRsp.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ExprQueryRsp.java
new file mode 100644
index 000000000000..addeeb1fd276
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ExprQueryRsp.java
@@ -0,0 +1,29 @@
+/*
+ * 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.promql.entity.response;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.ResponseData;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ExprQueryRsp extends QueryResponse {
+    private ResponseData data;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelValuesQueryRsp.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelValuesQueryRsp.java
new file mode 100644
index 000000000000..14299c86adc0
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelValuesQueryRsp.java
@@ -0,0 +1,30 @@
+/*
+ * 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.promql.entity.response;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class LabelValuesQueryRsp extends QueryResponse {
+    private final List<String> data = new ArrayList<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelsQueryRsp.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelsQueryRsp.java
new file mode 100644
index 000000000000..49dd537ececb
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/LabelsQueryRsp.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.promql.entity.response;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.LabelName;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class LabelsQueryRsp extends QueryResponse {
+    private final List<LabelName> data = new ArrayList<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetadataQueryRsp.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetadataQueryRsp.java
new file mode 100644
index 000000000000..2dab97bc53c4
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetadataQueryRsp.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.promql.entity.response;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.MetricMetadata;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MetadataQueryRsp extends QueryResponse {
+    private Map<String, List<MetricMetadata>> data = new HashMap<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricRspData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricRspData.java
new file mode 100644
index 000000000000..cf01d758e2d7
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricRspData.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.promql.entity.response;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.MetricData;
+import org.apache.skywalking.oap.query.promql.entity.ResponseData;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MetricRspData extends ResponseData {
+    private List<MetricData> result = new ArrayList<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricType.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricType.java
new file mode 100644
index 000000000000..7fe5f754b072
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/MetricType.java
@@ -0,0 +1,41 @@
+/*
+ * 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.promql.entity.response;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum MetricType {
+    GAUGE("gauge");
+
+    final String value;
+
+    MetricType(final String value) {
+        this.value = value;
+    }
+
+    @JsonValue
+    public String getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/QueryResponse.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/QueryResponse.java
new file mode 100644
index 000000000000..0fc9752b163c
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/QueryResponse.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.promql.entity.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+import org.apache.skywalking.oap.query.promql.entity.ErrorType;
+import org.apache.skywalking.oap.query.promql.entity.ResultStatus;
+
+@Data
+public class QueryResponse {
+    private ResultStatus status;
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private ErrorType errorType;
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private String error;
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private String warnings;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ResultType.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ResultType.java
new file mode 100644
index 000000000000..89da2d39eae3
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ResultType.java
@@ -0,0 +1,45 @@
+/*
+ * 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.promql.entity.response;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum ResultType {
+    NONE("none"),
+    VECTOR("vector"),
+    SCALAR("scalar"),
+    MATRIX("matrix"),
+    STRING("string");
+
+    final String value;
+
+    ResultType(final String value) {
+        this.value = value;
+    }
+
+    @JsonValue
+    public String getValue() {
+        return this.value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ScalarRspData.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ScalarRspData.java
new file mode 100644
index 000000000000..987787ba5eaf
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/ScalarRspData.java
@@ -0,0 +1,30 @@
+/*
+ * 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.promql.entity.response;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
+import org.apache.skywalking.oap.query.promql.entity.ResponseData;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ScalarRspData extends ResponseData {
+    private TimeValuePair result;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/SeriesQueryRsp.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/SeriesQueryRsp.java
new file mode 100644
index 000000000000..0a4edc5aa396
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/entity/response/SeriesQueryRsp.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.promql.entity.response;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class SeriesQueryRsp extends QueryResponse {
+    private List<MetricInfo> data = new ArrayList<>();
+}
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
new file mode 100644
index 000000000000..7a2dcc0ecac2
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/handler/PromQLApiHandler.java
@@ -0,0 +1,543 @@
+/*
+ * 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.promql.handler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpResponse;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.common.ResponseHeaders;
+import com.linecorp.armeria.server.annotation.Get;
+import com.linecorp.armeria.server.annotation.Param;
+import com.linecorp.armeria.server.annotation.Path;
+import com.linecorp.armeria.server.annotation.Post;
+import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+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.resolver.MetadataQueryV2;
+import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery;
+import org.apache.skywalking.oap.query.promql.entity.ErrorType;
+import org.apache.skywalking.oap.query.promql.entity.LabelName;
+import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
+import org.apache.skywalking.oap.query.promql.entity.MetricInstantData;
+import org.apache.skywalking.oap.query.promql.entity.MetricMetadata;
+import org.apache.skywalking.oap.query.promql.entity.MetricRangeData;
+import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
+import org.apache.skywalking.oap.query.promql.entity.response.ExprQueryRsp;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
+import org.apache.skywalking.oap.query.promql.entity.response.LabelValuesQueryRsp;
+import org.apache.skywalking.oap.query.promql.entity.response.LabelsQueryRsp;
+import org.apache.skywalking.oap.query.promql.entity.ResultStatus;
+import org.apache.skywalking.oap.query.promql.entity.response.MetricRspData;
+import org.apache.skywalking.oap.query.promql.entity.response.MetadataQueryRsp;
+import org.apache.skywalking.oap.query.promql.entity.response.MetricType;
+import org.apache.skywalking.oap.query.promql.entity.response.QueryResponse;
+import org.apache.skywalking.oap.query.promql.entity.response.ResultType;
+import org.apache.skywalking.oap.query.promql.entity.response.ScalarRspData;
+import org.apache.skywalking.oap.query.promql.entity.response.SeriesQueryRsp;
+import org.apache.skywalking.oap.query.promql.rt.PromOpUtils;
+import org.apache.skywalking.oap.query.promql.rt.PromQLMatchVisitor;
+import org.apache.skywalking.oap.query.promql.rt.exception.ParseErrorListener;
+import org.apache.skywalking.oap.query.promql.rt.result.MatcherSetResult;
+import org.apache.skywalking.oap.query.promql.rt.result.MetricsRangeResult;
+import org.apache.skywalking.oap.query.promql.rt.PromQLExprQueryVisitor;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ScalarResult;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.manual.endpoint.EndpointTraffic;
+import org.apache.skywalking.oap.server.core.analysis.manual.instance.InstanceTraffic;
+import org.apache.skywalking.oap.server.core.analysis.manual.service.ServiceTraffic;
+import org.apache.skywalking.oap.server.core.query.MetricDefinition;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.type.Endpoint;
+import org.apache.skywalking.oap.server.core.query.type.Service;
+import org.apache.skywalking.oap.server.core.query.type.ServiceInstance;
+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.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+import org.apache.skywalking.promql.rt.grammar.PromQLLexer;
+import org.apache.skywalking.promql.rt.grammar.PromQLParser;
+
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.formatDoubleValue;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.timestamp2Duration;
+
+public class PromQLApiHandler {
+    private final MetadataQueryV2 metadataQuery;
+    private final MetricsQuery metricsQuery;
+    private final RecordsQuery recordsQuery;
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    public PromQLApiHandler(ModuleManager moduleManager) {
+        this.metadataQuery = new MetadataQueryV2(moduleManager);
+        this.metricsQuery = new MetricsQuery(moduleManager);
+        this.recordsQuery = new RecordsQuery(moduleManager);
+    }
+
+    @Get
+    @Post
+    @Path("/api/v1/metadata")
+    public HttpResponse metadata(
+        @Param("limit") Optional<Integer> limit,
+        @Param("metric") Optional<String> metric) throws JsonProcessingException {
+        MetadataQueryRsp response = new MetadataQueryRsp();
+        response.setStatus(ResultStatus.SUCCESS);
+        String regex = metric.orElse("");
+        List<MetricDefinition> definitionList = metricsQuery.listMetrics(regex);
+        int maxNum = limit.orElse(definitionList.size());
+        for (int i = 0; i < maxNum; i++) {
+            List<MetricMetadata> metadataList = new ArrayList<>();
+            MetricMetadata metadata = new MetricMetadata(MetricType.GAUGE, "", "");
+            metadataList.add(metadata);
+            response.getData().put(definitionList.get(i).getName(), metadataList);
+        }
+        return jsonResponse(response);
+    }
+
+    /**
+     * OAP doesn't support query label by start/end,
+     * reserve these param to keep consistent with API protocol.
+     */
+    @Get
+    @Post
+    @Path("/api/v1/labels")
+    public HttpResponse labels(
+        @Param("match[]") Optional<String> match,
+        @Param("start") Optional<String> start,
+        @Param("end") Optional<String> end) throws IOException {
+        LabelsQueryRsp response = new LabelsQueryRsp();
+        if (match.isPresent()) {
+            PromQLLexer lexer = new PromQLLexer(
+                CharStreams.fromString(match.get()));
+            lexer.addErrorListener(ParseErrorListener.INSTANCE);
+            PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
+            parser.addErrorListener(ParseErrorListener.INSTANCE);
+            ParseTree tree;
+            try {
+                tree = parser.expression();
+            } catch (ParseCancellationException e) {
+                response.setStatus(ResultStatus.ERROR);
+                response.setErrorType(ErrorType.BAD_DATA);
+                response.setError(e.getMessage());
+                return jsonResponse(response);
+            }
+            PromQLMatchVisitor visitor = new PromQLMatchVisitor();
+            MatcherSetResult parseResult = visitor.visit(tree);
+            String metricName = parseResult.getMetricName();
+            Optional<ValueColumnMetadata.ValueColumn> valueColumn = ValueColumnMetadata.INSTANCE.readValueColumnDefinition(
+                metricName);
+            if (valueColumn.isPresent()) {
+                ValueColumnMetadata.ValueColumn metaData = valueColumn.get();
+                Scope scope = Scope.Finder.valueOf(metaData.getScopeId());
+                Column.ValueDataType dataType = metaData.getDataType();
+                response.getData().addAll(buildLabelNames(scope, dataType));
+            }
+        } else {
+            Arrays.stream(LabelName.values()).forEach(label -> {
+                response.getData().add(label);
+            });
+        }
+        response.setStatus(ResultStatus.SUCCESS);
+        return jsonResponse(response);
+    }
+
+    /**
+     * OAP doesn't support query label value by match/start/end,
+     * reserve these param to keep consistent with API protocol.
+     */
+    @Get
+    @Post
+    @Path("/api/v1/label/{label_name}/values")
+    public HttpResponse labelValues(
+        @Param("label_name") String labelName,
+        @Param("match[]") Optional<String> match,
+        @Param("start") Optional<String> start,
+        @Param("end") Optional<String> end) throws IOException {
+        LabelValuesQueryRsp response = new LabelValuesQueryRsp();
+        response.setStatus(ResultStatus.SUCCESS);
+
+        switch (LabelName.labelOf(labelName)) {
+            case NAME:
+                metricsQuery.listMetrics("").forEach(definition -> {
+                    response.getData().add(definition.getName());
+                });
+                break;
+            case LAYER:
+                for (Layer layer : Layer.values()) {
+                    response.getData().add(layer.name());
+                }
+                break;
+            case SCOPE:
+                for (Scope scope : Scope.values()) {
+                    response.getData().add(scope.name());
+                }
+                break;
+        }
+
+        return jsonResponse(response);
+    }
+
+    @Get
+    @Post
+    @Path("/api/v1/series")
+    public HttpResponse series(
+        @Param("match[]") String match,
+        @Param("start") String start,
+        @Param("end") String end) throws IOException {
+        long startTS = formatTimestamp2Millis(start);
+        long endTS = formatTimestamp2Millis(end);
+        Duration duration = timestamp2Duration(startTS, endTS);
+        SeriesQueryRsp response = new SeriesQueryRsp();
+        PromQLLexer lexer = new PromQLLexer(
+            CharStreams.fromString(match));
+        lexer.addErrorListener(ParseErrorListener.INSTANCE);
+        PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
+        parser.addErrorListener(ParseErrorListener.INSTANCE);
+        ParseTree tree;
+        try {
+            tree = parser.expression();
+        } catch (ParseCancellationException e) {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(ErrorType.BAD_DATA);
+            response.setError(e.getMessage());
+            return jsonResponse(response);
+        }
+        PromQLMatchVisitor visitor = new PromQLMatchVisitor();
+        MatcherSetResult parseResult = visitor.visit(tree);
+        String metricName = parseResult.getMetricName();
+
+        Optional<ValueColumnMetadata.ValueColumn> valueColumn = ValueColumnMetadata.INSTANCE.readValueColumnDefinition(
+            metricName);
+        if (valueColumn.isPresent()) {
+            ValueColumnMetadata.ValueColumn metaData = valueColumn.get();
+            Scope scope = Scope.Finder.valueOf(metaData.getScopeId());
+            Column.ValueDataType dataType = metaData.getDataType();
+            response.getData().add(buildMetaMetricInfo(metricName, scope, dataType));
+        } else if (Objects.equals(metricName, ServiceTraffic.INDEX_NAME)) {
+            String serviceName = parseResult.getLabelMap().get(LabelName.SERVICE);
+            if (StringUtil.isNotBlank(serviceName)) {
+                Service service = metadataQuery.findService(serviceName);
+                response.getData().add(buildMetricInfoFromTraffic(metricName, service));
+            } else {
+                List<Service> services = metadataQuery.listServices(
+                    parseResult.getLabelMap().get(LabelName.LAYER));
+                services.forEach(service -> {
+                    response.getData().add(buildMetricInfoFromTraffic(metricName, service));
+                });
+            }
+        } else if (Objects.equals(metricName, InstanceTraffic.INDEX_NAME)) {
+            String serviceName = parseResult.getLabelMap().get(LabelName.SERVICE);
+            String layer = parseResult.getLabelMap().get(LabelName.LAYER);
+            List<ServiceInstance> instances = metadataQuery.listInstances(
+                duration, IDManager.ServiceID.buildId(serviceName, Layer.valueOf(layer).isNormal()));
+            instances.forEach(instance -> {
+                response.getData().add(buildMetricInfoFromTraffic(metricName, instance));
+            });
+        } else if (Objects.equals(metricName, EndpointTraffic.INDEX_NAME)) {
+            String serviceName = parseResult.getLabelMap().get(LabelName.SERVICE);
+            String layer = parseResult.getLabelMap().get(LabelName.LAYER);
+            String keyword = parseResult.getLabelMap().getOrDefault(LabelName.KEYWORD, "");
+            String limit = parseResult.getLabelMap().getOrDefault(LabelName.LIMIT, "100");
+            List<Endpoint> endpoints = metadataQuery.findEndpoint(
+                keyword, IDManager.ServiceID.buildId(serviceName, Layer.valueOf(layer).isNormal()),
+                Integer.parseInt(limit)
+            );
+            endpoints.forEach(endpoint -> {
+                response.getData().add(buildMetricInfoFromTraffic(metricName, endpoint));
+            });
+        }
+
+        response.setStatus(ResultStatus.SUCCESS);
+        return jsonResponse(response);
+    }
+
+    @Get
+    @Post
+    @Path("/api/v1/query")
+    public HttpResponse query(
+        @Param("query") String query,
+        @Param("time") Optional<String> time,
+        @Param("timeout") Optional<String> timeout) throws IOException {
+        long endTS = System.currentTimeMillis();
+        if (time.isPresent()) {
+            endTS = formatTimestamp2Millis(time.get());
+        }
+        long startTS = endTS - 900000; //look back 15m by default
+        Duration duration = timestamp2Duration(startTS, endTS);
+        ExprQueryRsp response = new ExprQueryRsp();
+
+        PromQLLexer lexer = new PromQLLexer(
+            CharStreams.fromString(query));
+        lexer.addErrorListener(ParseErrorListener.INSTANCE);
+        PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
+        parser.addErrorListener(ParseErrorListener.INSTANCE);
+        ParseTree tree;
+        try {
+            tree = parser.expression();
+        } catch (ParseCancellationException e) {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(ErrorType.BAD_DATA);
+            response.setError(e.getMessage());
+            return jsonResponse(response);
+        }
+        PromQLExprQueryVisitor visitor = new PromQLExprQueryVisitor(
+            metricsQuery, recordsQuery, duration, QueryType.INSTANT);
+        ParseResult parseResult = visitor.visit(tree);
+
+        if (parseResult == null) {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(ErrorType.BAD_DATA);
+            response.setError("Bad expression, can not parse it.");
+        } else if (StringUtil.isBlank(parseResult.getErrorInfo())) {
+            //The expression include range, such as: metric_name[1m]
+            if (parseResult.isRangeExpression()) {
+                buildMatrixRsp(parseResult, response);
+            } else {
+                switch (parseResult.getResultType()) {
+                    case METRICS_RANGE:
+                        buildVectorRsp(parseResult, response);
+                        break;
+                    case SCALAR:
+                        buildScalarRsp(parseResult, response);
+                        break;
+                }
+            }
+        } else {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(parseResult.getErrorType());
+            response.setError(parseResult.getErrorInfo());
+        }
+        return jsonResponse(response);
+    }
+
+    /**
+     * OAP adopt time step by start/end,
+     * reserve step/timeout param to keep consistent with API protocol.
+     */
+    @Get
+    @Post
+    @Path("/api/v1/query_range")
+    public HttpResponse query_range(
+        @Param("query") String query,
+        @Param("start") String start,
+        @Param("end") String end,
+        @Param("step") String step,
+        @Param("timeout") Optional<String> timeout) throws IOException {
+        long startTS = formatTimestamp2Millis(start);
+        long endTS = formatTimestamp2Millis(end);
+        Duration duration = timestamp2Duration(startTS, endTS);
+        ExprQueryRsp response = new ExprQueryRsp();
+        PromQLLexer lexer = new PromQLLexer(
+            CharStreams.fromString(query));
+        lexer.addErrorListener(ParseErrorListener.INSTANCE);
+        PromQLParser parser = new PromQLParser(new CommonTokenStream(lexer));
+        parser.addErrorListener(ParseErrorListener.INSTANCE);
+        ParseTree tree;
+        try {
+            tree = parser.expression();
+        } catch (ParseCancellationException e) {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(ErrorType.BAD_DATA);
+            response.setError(e.getMessage());
+            return jsonResponse(response);
+        }
+
+        PromQLExprQueryVisitor visitor = new PromQLExprQueryVisitor(
+            metricsQuery, recordsQuery, duration, QueryType.RANGE);
+        ParseResult parseResult = visitor.visit(tree);
+
+        if (parseResult == null) {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(ErrorType.BAD_DATA);
+            response.setError("Bad expression, can not parse it.");
+        } else if (StringUtil.isBlank(parseResult.getErrorInfo())) {
+            switch (parseResult.getResultType()) {
+                case METRICS_RANGE:
+                    buildMatrixRsp(parseResult, response);
+                    break;
+                case SCALAR:
+                    buildScalarMatrixRsp(duration, parseResult, response);
+                    break;
+            }
+        } else {
+            response.setStatus(ResultStatus.ERROR);
+            response.setErrorType(parseResult.getErrorType());
+            response.setError(parseResult.getErrorInfo());
+        }
+        return jsonResponse(response);
+    }
+
+    private HttpResponse jsonResponse(QueryResponse response) throws JsonProcessingException {
+        return HttpResponse.of(ResponseHeaders.builder(HttpStatus.OK)
+                                              .contentType(MediaType.JSON)
+                                              .build(), HttpData.ofUtf8(MAPPER.writeValueAsString(response)));
+    }
+
+    private void buildVectorRsp(ParseResult parseResult, ExprQueryRsp response) {
+        MetricRspData exprRspData = new MetricRspData();
+        response.setData(exprRspData);
+        exprRspData.setResultType(ResultType.VECTOR);
+        MetricsRangeResult matrixResult = (MetricsRangeResult) parseResult;
+        response.setStatus(ResultStatus.SUCCESS);
+        matrixResult.getMetricDataList().forEach(rangData -> {
+            List<TimeValuePair> values = rangData.getValues();
+            if (values.size() > 0) {
+                MetricInstantData instantData = new MetricInstantData();
+                instantData.setValue(values.get(values.size() - 1));
+                instantData.setMetric(rangData.getMetric());
+                exprRspData.getResult().add(instantData);
+            }
+        });
+    }
+
+    private void buildScalarRsp(ParseResult parseResult, ExprQueryRsp response) {
+        ScalarRspData scalarRspData = new ScalarRspData();
+        response.setData(scalarRspData);
+        scalarRspData.setResultType(ResultType.SCALAR);
+        ScalarResult scalarResult = (ScalarResult) parseResult;
+        response.setStatus(ResultStatus.SUCCESS);
+        //need return int type
+        scalarRspData.setResult(
+            new TimeValuePair(
+                System.currentTimeMillis() / 1000,
+                formatDoubleValue(scalarResult.getValue())
+            ));
+    }
+
+    private void buildMatrixRsp(ParseResult parseResult, ExprQueryRsp response) {
+        MetricRspData responseData = new MetricRspData();
+        responseData.setResultType(ResultType.MATRIX);
+        response.setData(responseData);
+        MetricsRangeResult matrixResult = (MetricsRangeResult) parseResult;
+        response.setStatus(ResultStatus.SUCCESS);
+        responseData.getResult().addAll(matrixResult.getMetricDataList());
+    }
+
+    private void buildScalarMatrixRsp(Duration duration, ParseResult parseResult, ExprQueryRsp response) {
+        MetricRspData responseData = new MetricRspData();
+        responseData.setResultType(ResultType.MATRIX);
+        response.setData(responseData);
+        ScalarResult scalarResult = (ScalarResult) parseResult;
+        response.setStatus(ResultStatus.SUCCESS);
+        MetricRangeData metricData = new MetricRangeData();
+        metricData.setValues(
+            PromOpUtils.buildMatrixValues(duration, formatDoubleValue(scalarResult.getValue())));
+        responseData.getResult().add(metricData);
+    }
+
+    private static long formatTimestamp2Millis(String timestamp) {
+        return Double.valueOf(timestamp).longValue() * 1000;
+    }
+
+    private List<LabelName> buildLabelNames(Scope scope,
+                                            Column.ValueDataType dataType) {
+        List<LabelName> labelNames = new ArrayList<>();
+        labelNames.add(LabelName.LAYER);
+        labelNames.add(LabelName.SCOPE);
+        labelNames.add(LabelName.TOP_N);
+        labelNames.add(LabelName.ORDER);
+        if (Column.ValueDataType.LABELED_VALUE == dataType) {
+            labelNames.add(LabelName.LABELS);
+            labelNames.add(LabelName.RELABELS);
+        }
+        switch (scope) {
+            case ServiceInstance:
+                labelNames.add(LabelName.SERVICE_INSTANCE);
+                labelNames.add(LabelName.PARENT_SERVICE);
+                break;
+            case Endpoint:
+                labelNames.add(LabelName.ENDPOINT);
+                labelNames.add(LabelName.PARENT_SERVICE);
+                break;
+        }
+        return labelNames;
+    }
+
+    //Only return label names, don't support list the product of all the labels and label values.
+    private MetricInfo buildMetaMetricInfo(String metricName,
+                                           Scope scope,
+                                           Column.ValueDataType dataType) {
+        MetricInfo metricInfo = new MetricInfo(metricName);
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.LAYER, ""));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.TOP_N, ""));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.ORDER, ""));
+
+        if (Column.ValueDataType.LABELED_VALUE == dataType) {
+            metricInfo.getLabels().add(new LabelValuePair(LabelName.LABELS, ""));
+            metricInfo.getLabels().add(new LabelValuePair(LabelName.RELABELS, ""));
+        }
+        switch (scope) {
+            case Service:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Service.name()));
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE, ""));
+                break;
+            case ServiceInstance:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.ServiceInstance.name()));
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE_INSTANCE, ""));
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.PARENT_SERVICE, ""));
+                break;
+            case Endpoint:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Endpoint.name()));
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.ENDPOINT, ""));
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.PARENT_SERVICE, ""));
+                break;
+        }
+        return metricInfo;
+    }
+
+    private MetricInfo buildMetricInfoFromTraffic(String metricName, Service service) {
+        MetricInfo metricInfo = new MetricInfo(metricName);
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE, service.getName()));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Service.name()));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.LAYER, service.getLayers().iterator().next()));
+        return metricInfo;
+    }
+
+    private MetricInfo buildMetricInfoFromTraffic(String metricName, ServiceInstance instance) {
+        MetricInfo metricInfo = new MetricInfo(metricName);
+        metricInfo.getLabels()
+                  .add(new LabelValuePair(LabelName.SERVICE_INSTANCE, instance.getName()));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.ServiceInstance.name()));
+        return metricInfo;
+    }
+
+    private MetricInfo buildMetricInfoFromTraffic(String metricName, Endpoint endpoint) {
+        MetricInfo metricInfo = new MetricInfo(metricName);
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.ENDPOINT, endpoint.getName()));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Endpoint.name()));
+        return metricInfo;
+    }
+
+    public enum QueryType {
+        INSTANT,
+        RANGE,
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
new file mode 100644
index 000000000000..03da30769d8e
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromOpUtils.java
@@ -0,0 +1,255 @@
+/*
+ * 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.promql.rt;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.skywalking.oap.query.promql.entity.MetricRangeData;
+import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
+import org.apache.skywalking.oap.query.promql.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
+import org.apache.skywalking.oap.query.promql.rt.result.MetricsRangeResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ScalarResult;
+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.Step;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.promql.rt.grammar.PromQLParser;
+import org.joda.time.DateTime;
+
+public class PromOpUtils {
+    //Adopt skywalking time step.
+    public static Duration timestamp2Duration(long startTS, long endTS) {
+        Duration duration = new Duration();
+        if (endTS < startTS) {
+            throw new IllegalArgumentException("End time must not be before start");
+        }
+        DateTime startDT = new DateTime(startTS);
+        DateTime endDT = new DateTime(endTS);
+
+        long durationValue = endTS - startTS;
+
+        if (durationValue < 3600000) {
+            duration.setStep(Step.MINUTE);
+            duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD_HHMM));
+            duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD_HHMM));
+        } else if (durationValue < 86400000) {
+            duration.setStep(Step.HOUR);
+            duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD_HH));
+            duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD_HH));
+        } else {
+            duration.setStep(Step.DAY);
+            duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD));
+            duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD));
+        }
+        return duration;
+    }
+
+    static MetricsRangeResult matrixScalarBinaryOp(MetricsRangeResult matrix, ScalarResult scalar, int opType) {
+        MetricsRangeResult result = new MetricsRangeResult();
+        result.setResultType(ParseResultType.METRICS_RANGE);
+        matrix.getMetricDataList().forEach(metricData -> {
+            MetricRangeData newData = new MetricRangeData();
+            result.getMetricDataList().add(newData);
+            newData.setMetric(metricData.getMetric());
+            List<TimeValuePair> newValues = metricData.getValues().stream().map(value -> {
+                double v = Double.parseDouble(value.getValue());
+                return new TimeValuePair(
+                    value.getTime(),
+                    formatDoubleValue(scalarBinaryOp(v, scalar.getValue(), opType))
+                );
+
+            }).collect(Collectors.toList());
+            newData.setValues(newValues);
+        });
+        return result;
+    }
+
+    static MetricsRangeResult matrixBinaryOp(MetricsRangeResult matrixLeft,
+                                             MetricsRangeResult matrixRight,
+                                             int opType) throws IllegalExpressionException {
+        MetricsRangeResult result = new MetricsRangeResult();
+        result.setResultType(ParseResultType.METRICS_RANGE);
+        for (int i = 0; i < matrixLeft.getMetricDataList().size(); i++) {
+            MetricRangeData dataLeft = matrixLeft.getMetricDataList().get(i);
+            MetricRangeData dataRight = matrixRight.getMetricDataList().get(i);
+            if (!dataLeft.getMetric().equals(dataRight.getMetric())) {
+                throw new IllegalExpressionException(
+                    "The metric info result left in conformity with right.");
+            }
+            if (dataLeft.getValues().size() != dataRight.getValues().size()) {
+                throw new IllegalExpressionException(
+                    "The metric value range left in conformity with right.");
+            }
+            MetricRangeData newData = new MetricRangeData();
+            result.getMetricDataList().add(newData);
+            newData.setMetric(dataLeft.getMetric());
+            List<TimeValuePair> newValues = new ArrayList<>();
+            newData.setValues(newValues);
+            for (int j = 0; j < dataLeft.getValues().size(); j++) {
+                double lv = Double.parseDouble(dataLeft.getValues().get(j).getValue());
+                double rv = Double.parseDouble(dataRight.getValues().get(j).getValue());
+                newValues.add(new TimeValuePair(
+                    dataLeft.getValues().get(j).getTime(),
+                    formatDoubleValue(scalarBinaryOp(lv, rv, opType))
+                ));
+            }
+        }
+        return result;
+    }
+
+    static double scalarBinaryOp(double leftValue, double rightValue, int opType) {
+        double calculatedResult = 0;
+        switch (opType) {
+            case PromQLParser.ADD:
+                calculatedResult = leftValue + rightValue;
+                break;
+            case PromQLParser.SUB:
+                calculatedResult = leftValue - rightValue;
+                break;
+            case PromQLParser.MUL:
+                calculatedResult = leftValue * rightValue;
+                break;
+            case PromQLParser.DIV:
+                calculatedResult = leftValue / rightValue;
+                break;
+            case PromQLParser.MOD:
+                calculatedResult = leftValue % rightValue;
+                break;
+        }
+        return calculatedResult;
+    }
+
+    static int scalarCompareOp(double leftValue, double rightValue, int opType) {
+        int comparedResult = 0;
+        switch (opType) {
+            case PromQLParser.DEQ:
+                comparedResult = boolToInt(leftValue == rightValue);
+                break;
+            case PromQLParser.NEQ:
+                comparedResult = boolToInt(leftValue != rightValue);
+                break;
+            case PromQLParser.GT:
+                comparedResult = boolToInt(leftValue > rightValue);
+                break;
+            case PromQLParser.LT:
+                comparedResult = boolToInt(leftValue < rightValue);
+                break;
+            case PromQLParser.GTE:
+                comparedResult = boolToInt(leftValue >= rightValue);
+                break;
+            case PromQLParser.LTE:
+                comparedResult = boolToInt(leftValue <= rightValue);
+                break;
+        }
+        return comparedResult;
+    }
+
+    private static int boolToInt(boolean v) {
+        return v ? 1 : 0;
+    }
+
+    static MetricsRangeResult matrixScalarCompareOp(MetricsRangeResult matrix, ScalarResult scalar, int opType) {
+        MetricsRangeResult result = new MetricsRangeResult();
+        result.setResultType(ParseResultType.METRICS_RANGE);
+        matrix.getMetricDataList().forEach(metricData -> {
+            MetricRangeData newData = new MetricRangeData();
+            result.getMetricDataList().add(newData);
+            newData.setMetric(metricData.getMetric());
+            List<TimeValuePair> newValues = metricData.getValues()
+                                                      .stream()
+                                                      .filter(timeValuePair -> scalarCompareOp(
+                                                          Double.parseDouble(timeValuePair.getValue()),
+                                                          scalar.getValue(), opType
+                                                      ) == 1)
+                                                      .collect(
+                                                          Collectors.toList());
+            newData.setValues(newValues);
+        });
+        return result;
+    }
+
+    static MetricsRangeResult matrixCompareOp(MetricsRangeResult matrixLeft,
+                                              MetricsRangeResult matrixRight,
+                                              int opType) throws IllegalExpressionException {
+        MetricsRangeResult result = new MetricsRangeResult();
+        result.setResultType(ParseResultType.METRICS_RANGE);
+        for (int i = 0; i < matrixLeft.getMetricDataList().size(); i++) {
+            MetricRangeData dataLeft = matrixLeft.getMetricDataList().get(i);
+            MetricRangeData dataRight = matrixRight.getMetricDataList().get(i);
+            if (!dataLeft.getMetric().equals(dataRight.getMetric())) {
+                throw new IllegalExpressionException(
+                    "The metric info result left in conformity with right.");
+            }
+            if (dataLeft.getValues().size() != dataRight.getValues().size()) {
+                throw new IllegalExpressionException(
+                    "The metric value range left in conformity with right.");
+            }
+            MetricRangeData newData = new MetricRangeData();
+            result.getMetricDataList().add(newData);
+            newData.setMetric(dataLeft.getMetric());
+            List<TimeValuePair> newValues = new ArrayList<>();
+            newData.setValues(newValues);
+            for (int j = 0; j < dataLeft.getValues().size(); j++) {
+                double lv = Double.parseDouble(dataLeft.getValues().get(j).getValue());
+                double rv = Double.parseDouble(dataRight.getValues().get(j).getValue());
+                if (scalarCompareOp(lv, rv, opType) == 1) {
+                    newValues.add(dataLeft.getValues().get(j));
+                }
+            }
+        }
+        return result;
+    }
+
+    public static List<TimeValuePair> buildMatrixValues(Duration duration, String singleValue) {
+        List<PointOfTime> times = duration.assembleDurationPoints();
+        List<TimeValuePair> values = new ArrayList<>(times.size());
+        for (PointOfTime time : times) {
+            long retTimestampSec = DurationUtils.INSTANCE.parseToDateTime(
+                                                    duration.getStep(), time.getPoint())
+                                                         .getMillis() / 1000;
+            TimeValuePair value = new TimeValuePair(
+                retTimestampSec, singleValue);
+            values.add(value);
+        }
+        return values;
+    }
+
+    static List<TimeValuePair> buildMatrixValues(Duration duration, MetricsValues metricsValues) {
+        List<PointOfTime> times = duration.assembleDurationPoints();
+        List<TimeValuePair> values = new ArrayList<>(times.size());
+        for (int i = 0; i < times.size(); i++) {
+            long retTimestampSec = DurationUtils.INSTANCE.parseToDateTime(
+                                                    duration.getStep(), times.get(i).getPoint())
+                                                         .getMillis() / 1000;
+            TimeValuePair value = new TimeValuePair(
+                retTimestampSec, Long.toString(metricsValues.getValues().getValues().get(i).getValue()));
+            values.add(value);
+        }
+        return values;
+    }
+
+    public static String formatDoubleValue(double v) {
+        DecimalFormat format = new DecimalFormat("#.##");
+        return format.format(v);
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
new file mode 100644
index 000000000000..68c11aaf5e52
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLExprQueryVisitor.java
@@ -0,0 +1,524 @@
+/*
+ * 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.promql.rt;
+
+import java.io.IOException;
+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.resolver.MetricsQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery;
+import org.apache.skywalking.oap.query.promql.entity.ErrorType;
+import org.apache.skywalking.oap.query.promql.entity.LabelName;
+import org.apache.skywalking.oap.query.promql.entity.LabelValuePair;
+import org.apache.skywalking.oap.query.promql.entity.MetricRangeData;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
+import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler;
+import org.apache.skywalking.oap.query.promql.rt.exception.IllegalExpressionException;
+import org.apache.skywalking.oap.query.promql.rt.result.MetricsRangeResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
+import org.apache.skywalking.oap.query.promql.rt.result.ScalarResult;
+import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
+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.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.promql.rt.grammar.PromQLParser;
+import org.apache.skywalking.promql.rt.grammar.PromQLParserBaseVisitor;
+
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.buildMatrixValues;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixBinaryOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixCompareOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixScalarBinaryOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.matrixScalarCompareOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.scalarBinaryOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.scalarCompareOp;
+import static org.apache.skywalking.oap.query.promql.rt.PromOpUtils.timestamp2Duration;
+
+@Slf4j
+public class PromQLExprQueryVisitor extends PromQLParserBaseVisitor<ParseResult> {
+    private final MetricsQuery metricsQuery;
+    private final RecordsQuery recordsQuery;
+    private final PromQLApiHandler.QueryType queryType;
+    private Duration duration;
+
+    public PromQLExprQueryVisitor(final MetricsQuery metricsQuery,
+                                  final RecordsQuery recordsQuery,
+                                  final Duration duration,
+                                  final PromQLApiHandler.QueryType queryType) {
+        this.metricsQuery = metricsQuery;
+        this.recordsQuery = recordsQuery;
+        this.duration = duration;
+        this.queryType = queryType;
+    }
+
+    @Override
+    public ParseResult visitAddSubOp(PromQLParser.AddSubOpContext ctx) {
+        ParseResult left = visit(ctx.expression(0));
+        if (StringUtil.isNotBlank(left.getErrorInfo())) {
+            return left;
+        }
+        ParseResult right = visit(ctx.expression(1));
+        if (StringUtil.isNotBlank(right.getErrorInfo())) {
+            return right;
+        }
+        int opType = ctx.addSub().getStart().getType();
+        return binaryOp(left, right, opType);
+    }
+
+    @Override
+    public ParseResult visitMulDivModOp(PromQLParser.MulDivModOpContext ctx) {
+        ParseResult left = visit(ctx.expression(0));
+        if (StringUtil.isNotBlank(left.getErrorInfo())) {
+            return left;
+        }
+        ParseResult right = visit(ctx.expression(1));
+        if (StringUtil.isNotBlank(right.getErrorInfo())) {
+            return right;
+        }
+        int opType = ctx.mulDivMod().getStart().getType();
+        return binaryOp(left, right, opType);
+    }
+
+    @Override
+    public ParseResult visitCompareOp(PromQLParser.CompareOpContext ctx) {
+        ParseResult left = visit(ctx.expression(0));
+        if (StringUtil.isNotBlank(left.getErrorInfo())) {
+            return left;
+        }
+        ParseResult right = visit(ctx.expression(1));
+        if (StringUtil.isNotBlank(right.getErrorInfo())) {
+            return right;
+        }
+        boolean boolModifier = ctx.compare().BOOL() != null;
+        int opType = ctx.compare().getStart().getType();
+        return compareOp(left, right, opType, boolModifier);
+    }
+
+    private ParseResult compareOp(ParseResult left, ParseResult right, int opType, boolean boolModifier) {
+        try {
+            if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.SCALAR) {
+                if (!boolModifier) {
+                    throw new IllegalExpressionException("Comparisons between scalars must use BOOL modifier.");
+                } else {
+                    ScalarResult scalarLeft = (ScalarResult) left;
+                    ScalarResult scalarRight = (ScalarResult) right;
+                    int value = scalarCompareOp(scalarLeft.getValue(), scalarRight.getValue(), opType);
+                    ScalarResult scalarResult = new ScalarResult();
+                    scalarResult.setValue(value);
+                    scalarResult.setResultType(ParseResultType.SCALAR);
+                    return scalarResult;
+                }
+            } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.SCALAR) {
+                return matrixScalarCompareOp((MetricsRangeResult) left, (ScalarResult) right, opType);
+            } else if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.METRICS_RANGE) {
+                return matrixScalarCompareOp((MetricsRangeResult) right, (ScalarResult) left, opType);
+            } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.METRICS_RANGE) {
+                try {
+                    return matrixCompareOp((MetricsRangeResult) left, (MetricsRangeResult) right, opType);
+                } catch (IllegalExpressionException e) {
+                    MetricsRangeResult result = new MetricsRangeResult();
+                    result.setErrorType(ErrorType.BAD_DATA);
+                    result.setErrorInfo(e.getMessage());
+                    return result;
+                }
+            }
+        } catch (IllegalExpressionException e) {
+            MetricsRangeResult result = new MetricsRangeResult();
+            result.setErrorType(ErrorType.BAD_DATA);
+            result.setErrorInfo(e.getMessage());
+            return result;
+        }
+        return new ParseResult();
+    }
+
+    @Override
+    public ParseResult visitNumberLiteral(PromQLParser.NumberLiteralContext ctx) {
+        ScalarResult result = new ScalarResult();
+        double value = Double.parseDouble(ctx.NUMBER().getText());
+        result.setValue(value);
+        result.setResultType(ParseResultType.SCALAR);
+        return result;
+    }
+
+    @Override
+    public ParseResult visitMetricInstant(PromQLParser.MetricInstantContext ctx) {
+        ParseResult result = new ParseResult();
+        try {
+            String metricName = ctx.metricName().getText();
+            Optional<ValueColumnMetadata.ValueColumn> valueColumn = getValueColumn(metricName);
+            if (!valueColumn.isPresent()) {
+                result.setErrorType(ErrorType.BAD_DATA);
+                result.setErrorInfo("Metric: [" + metricName + "] dose not exist.");
+                return result;
+            }
+            if (ctx.labelList() == null) {
+                result.setErrorType(ErrorType.BAD_DATA);
+                result.setErrorInfo("No labels found in the expression.");
+                return result;
+            }
+            Map<LabelName, String> labelMap = new HashMap<>();
+            for (PromQLParser.LabelContext labelCtx : ctx.labelList().label()) {
+                String labelName = labelCtx.labelName().getText();
+                String labelValue = labelCtx.labelValue().getText();
+                String labelValueTrim = labelValue.substring(1, labelValue.length() - 1);
+                try {
+                    labelMap.put(LabelName.labelOf(labelName), labelValueTrim);
+                } catch (IllegalArgumentException e) {
+                    throw new IllegalExpressionException("Label:[" + labelName + "] is illegal.");
+                }
+            }
+            final Layer layer;
+            checkLabels(labelMap, LabelName.LAYER);
+            try {
+                layer = Layer.valueOf(labelMap.get(LabelName.LAYER));
+            } catch (IllegalArgumentException e) {
+                throw new IllegalExpressionException(
+                    "Layer:[" + labelMap.get(LabelName.LAYER) + "] is missing or illegal.");
+            }
+            ValueColumnMetadata.ValueColumn metaData = valueColumn.get();
+            Scope scope = Scope.Finder.valueOf(metaData.getScopeId());
+            Column.ValueDataType dataType = metaData.getDataType();
+            MetricsRangeResult matrixResult = new MetricsRangeResult();
+            matrixResult.setResultType(ParseResultType.METRICS_RANGE);
+            if (StringUtil.isNotBlank(labelMap.get(LabelName.TOP_N))) {
+                if (Column.ValueDataType.SAMPLED_RECORD == dataType) {
+                    queryRecords(metricName, layer, scope, labelMap, matrixResult);
+                } else {
+                    queryTopN(metricName, layer, scope, labelMap, matrixResult);
+                }
+            } else {
+                if (Column.ValueDataType.COMMON_VALUE == dataType) {
+                    metricsValuesQuery(metricName, layer, scope, labelMap, matrixResult);
+                } else if (Column.ValueDataType.LABELED_VALUE == dataType) {
+                    labeledMetricsValuesQuery(metricName, layer, scope, labelMap, matrixResult);
+                }
+            }
+            return matrixResult;
+        } catch (IllegalExpressionException e) {
+            result.setErrorType(ErrorType.BAD_DATA);
+            result.setErrorInfo(e.getMessage());
+            return result;
+        } catch (IOException e) {
+            result.setErrorType(ErrorType.INTERNAL);
+            result.setErrorInfo("Internal IO exception.");
+            log.error("Query metrics error.", e);
+            return result;
+        }
+    }
+
+    @Override
+    public ParseResult visitMetricRange(PromQLParser.MetricRangeContext ctx) {
+        if (PromQLApiHandler.QueryType.RANGE == queryType) {
+            ParseResult result = new ParseResult();
+            result.setErrorType(ErrorType.BAD_DATA);
+            result.setErrorInfo("Range expression should use instant query.");
+            return result;
+        }
+
+        String timeRange = "PT" + ctx.DURATION().getText().toUpperCase();
+        long endTS = System.currentTimeMillis();
+        long startTS = endTS - java.time.Duration.parse(timeRange).toMillis();
+        duration = timestamp2Duration(startTS, endTS);
+        ParseResult result = visit(ctx.metricInstant());
+        result.setRangeExpression(true);
+        return result;
+    }
+
+    private void checkLabels(Map<LabelName, String> labelMap,
+                             LabelName... labelNames) throws IllegalExpressionException {
+        StringBuilder missLabels = new StringBuilder();
+        for (int i = 0; i < labelNames.length; i++) {
+            String labelName = labelNames[i].toString();
+            if (labelMap.get(labelNames[i]) == null) {
+                if (i == 0) {
+                    missLabels.append(labelName);
+                } else {
+                    missLabels.append(",").append(labelName);
+                }
+            }
+        }
+        String result = missLabels.toString();
+        if (StringUtil.isNotBlank(result)) {
+            throw new IllegalExpressionException("Metrics expression missing label: " + result);
+        }
+    }
+
+    private void queryTopN(String metricName,
+                           Layer layer,
+                           Scope scope,
+                           Map<LabelName, String> labelMap,
+                           MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException {
+        TopNCondition topNCondition = buildTopNCondition(metricName, layer, scope, labelMap);
+        List<SelectedRecord> selectedRecords = metricsQuery.sortMetrics(topNCondition, duration);
+        for (SelectedRecord selectedRecord : selectedRecords) {
+            MetricRangeData metricData = new MetricRangeData();
+            MetricInfo metricInfo = buildMetricInfo(metricName, layer, scope, labelMap,
+                                                    Optional.empty(),
+                                                    Optional.ofNullable(selectedRecord.getName()), Optional.empty()
+            );
+            metricData.setMetric(metricInfo);
+            metricData.setValues(buildMatrixValues(duration, selectedRecord.getValue()));
+            matrixResult.getMetricDataList().add(metricData);
+        }
+    }
+
+    private void queryRecords(String metricName,
+                              Layer layer,
+                              Scope scope,
+                              Map<LabelName, String> labelMap,
+                              MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException {
+        RecordCondition recordCondition = buildRecordCondition(metricName, layer, scope, labelMap);
+        List<Record> records = recordsQuery.readRecords(recordCondition, duration);
+        for (Record record : records) {
+            MetricRangeData metricData = new MetricRangeData();
+            MetricInfo metricInfo = buildMetricInfo(metricName, layer, scope, labelMap,
+                                                    Optional.empty(), Optional.empty(),
+                                                    Optional.ofNullable(record.getName())
+            );
+            metricData.setMetric(metricInfo);
+            metricData.setValues(buildMatrixValues(duration, record.getValue()));
+            matrixResult.getMetricDataList().add(metricData);
+        }
+    }
+
+    private void metricsValuesQuery(String metricName,
+                                    Layer layer,
+                                    Scope scope,
+                                    Map<LabelName, String> labelMap,
+                                    MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException {
+        MetricsCondition metricsCondition = buildMetricsCondition(metricName, layer, scope, labelMap);
+        MetricsValues metricsValues = metricsQuery.readMetricsValues(
+            metricsCondition, duration);
+        MetricRangeData metricData = new MetricRangeData();
+        MetricInfo metricInfo = buildMetricInfo(
+            metricName, layer, scope, labelMap, Optional.empty(), Optional.empty(), Optional.empty());
+        metricData.setMetric(metricInfo);
+        metricData.setValues(buildMatrixValues(duration, metricsValues));
+        matrixResult.getMetricDataList().add(metricData);
+    }
+
+    private void labeledMetricsValuesQuery(String metricName,
+                                           Layer layer,
+                                           Scope scope,
+                                           Map<LabelName, String> labelMap,
+                                           MetricsRangeResult matrixResult) throws IOException, IllegalExpressionException {
+        MetricsCondition metricsCondition = buildMetricsCondition(metricName, layer, scope, labelMap);
+        Map<String, String> relabelMap = new HashMap<>();
+        String queryLabels = labelMap.get(LabelName.LABELS);
+        List<String> queryLabelList = Collections.EMPTY_LIST;
+        if (StringUtil.isNotBlank(queryLabels)) {
+            queryLabelList = Arrays.asList(queryLabels.split(Const.COMMA));
+
+            String relabels = labelMap.get(LabelName.RELABELS);
+            List<String> relabelList = Collections.EMPTY_LIST;
+            if (StringUtil.isNotBlank(relabels)) {
+                relabelList = Arrays.asList(relabels.split(Const.COMMA));
+            }
+            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));
+            }
+        }
+        List<MetricsValues> metricsValuesList = metricsQuery.readLabeledMetricsValues(
+            metricsCondition, queryLabelList, duration);
+
+        for (MetricsValues metricsValues : metricsValuesList) {
+            MetricRangeData metricData = new MetricRangeData();
+            MetricInfo metricInfo = buildMetricInfo(
+                metricName, layer, scope, labelMap,
+                Optional.ofNullable(relabelMap.getOrDefault(
+                    metricsValues.getLabel(),
+                    metricsValues.getLabel()
+                )),
+                Optional.empty(),
+                Optional.empty()
+            );
+            metricData.setMetric(metricInfo);
+            metricData.setValues(buildMatrixValues(duration, metricsValues));
+            matrixResult.getMetricDataList().add(metricData);
+        }
+    }
+
+    private ParseResult binaryOp(ParseResult left, ParseResult right, int opType) {
+        if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.SCALAR) {
+            ScalarResult scalarLeft = (ScalarResult) left;
+            ScalarResult scalarRight = (ScalarResult) right;
+            double value = scalarBinaryOp(scalarLeft.getValue(), scalarRight.getValue(), opType);
+            ScalarResult scalarResult = new ScalarResult();
+            scalarResult.setValue(value);
+            scalarResult.setResultType(ParseResultType.SCALAR);
+            return scalarResult;
+        } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.SCALAR) {
+            return matrixScalarBinaryOp((MetricsRangeResult) left, (ScalarResult) right, opType);
+        } else if (left.getResultType() == ParseResultType.SCALAR && right.getResultType() == ParseResultType.METRICS_RANGE) {
+            return matrixScalarBinaryOp((MetricsRangeResult) right, (ScalarResult) left, opType);
+        } else if (left.getResultType() == ParseResultType.METRICS_RANGE && right.getResultType() == ParseResultType.METRICS_RANGE) {
+            try {
+                return matrixBinaryOp((MetricsRangeResult) left, (MetricsRangeResult) right, opType);
+            } catch (IllegalExpressionException e) {
+                MetricsRangeResult result = new MetricsRangeResult();
+                result.setErrorType(ErrorType.BAD_DATA);
+                result.setErrorInfo(e.getMessage());
+                return result;
+            }
+        }
+        return new ParseResult();
+    }
+
+    private Entity buildEntity(Layer layer,
+                               Scope scope,
+                               String serviceName,
+                               Map<LabelName, String> labelMap) throws IllegalExpressionException {
+        Entity entity = new Entity();
+        entity.setScope(scope);
+        entity.setNormal(layer.isNormal());
+        entity.setServiceName(serviceName);
+        switch (scope) {
+            case ServiceInstance:
+                checkLabels(labelMap, LabelName.SERVICE_INSTANCE);
+                entity.setServiceInstanceName(labelMap.get(LabelName.SERVICE_INSTANCE));
+                break;
+            case Endpoint:
+                checkLabels(labelMap, LabelName.ENDPOINT);
+                entity.setEndpointName(labelMap.get(LabelName.ENDPOINT));
+                break;
+        }
+        return entity;
+    }
+
+    private TopNCondition buildTopNCondition(String metricName,
+                                             Layer layer,
+                                             Scope scope,
+                                             Map<LabelName, String> labelMap) throws IllegalExpressionException {
+        checkLabels(labelMap, LabelName.TOP_N, LabelName.PARENT_SERVICE, LabelName.ORDER);
+        TopNCondition topNCondition = new TopNCondition();
+        topNCondition.setName(metricName);
+        topNCondition.setParentService(labelMap.get(LabelName.PARENT_SERVICE));
+        topNCondition.setTopN(Integer.parseInt(labelMap.get(LabelName.TOP_N)));
+        topNCondition.setOrder(Order.valueOf(labelMap.get(LabelName.ORDER)));
+        topNCondition.setNormal(layer.isNormal());
+        topNCondition.setScope(scope);
+        return topNCondition;
+    }
+
+    private RecordCondition buildRecordCondition(String metricName,
+                                                 Layer layer,
+                                                 Scope scope,
+                                                 Map<LabelName, String> labelMap) throws IllegalExpressionException {
+        checkLabels(labelMap, LabelName.TOP_N, LabelName.PARENT_SERVICE, LabelName.ORDER);
+        String parentServiceName = labelMap.get(LabelName.PARENT_SERVICE);
+        RecordCondition recordCondition = new RecordCondition();
+        recordCondition.setName(metricName);
+        recordCondition.setParentEntity(buildEntity(layer, scope, parentServiceName, labelMap));
+        recordCondition.setTopN(Integer.parseInt(labelMap.get(LabelName.TOP_N)));
+        recordCondition.setOrder(Order.valueOf(labelMap.get(LabelName.ORDER)));
+        return recordCondition;
+    }
+
+    private MetricsCondition buildMetricsCondition(String metricName,
+                                                   Layer layer,
+                                                   Scope scope,
+                                                   Map<LabelName, String> labelMap) throws IllegalExpressionException {
+        checkLabels(labelMap, LabelName.SERVICE);
+        String serviceName = labelMap.get(LabelName.SERVICE);
+        MetricsCondition metricsCondition = new MetricsCondition();
+        metricsCondition.setEntity(buildEntity(layer, scope, serviceName, labelMap));
+        metricsCondition.setName(metricName);
+        return metricsCondition;
+    }
+
+    private MetricInfo buildMetricInfo(String metricName,
+                                       Layer layer,
+                                       Scope scope,
+                                       Map<LabelName, String> labelMap,
+                                       Optional<String> valueLabel,
+                                       Optional<String> topNEntityName,
+                                       Optional<String> recordName) throws IllegalExpressionException {
+
+        MetricInfo metricInfo = new MetricInfo(metricName);
+        valueLabel.ifPresent(s -> metricInfo.getLabels().add(new LabelValuePair(LabelName.LABEL, s)));
+        metricInfo.getLabels().add(new LabelValuePair(LabelName.LAYER, layer.name()));
+        switch (scope) {
+            case Service:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Service.name()));
+                if (topNEntityName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE, topNEntityName.get()));
+                } else if (recordName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get()));
+                } else {
+                    checkLabels(labelMap, LabelName.SERVICE);
+                    metricInfo.getLabels()
+                              .add(new LabelValuePair(LabelName.SERVICE, labelMap.get(LabelName.SERVICE)));
+                }
+                break;
+            case ServiceInstance:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.ServiceInstance.name()));
+                if (topNEntityName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.SERVICE_INSTANCE, topNEntityName.get()));
+                } else if (recordName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get()));
+                } else {
+                    checkLabels(labelMap, LabelName.SERVICE_INSTANCE);
+                    metricInfo.getLabels()
+                              .add(new LabelValuePair(
+                                  LabelName.SERVICE_INSTANCE,
+                                  labelMap.get(LabelName.SERVICE_INSTANCE)
+                              ));
+                }
+                break;
+            case Endpoint:
+                metricInfo.getLabels().add(new LabelValuePair(LabelName.SCOPE, Scope.Endpoint.name()));
+                if (topNEntityName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.ENDPOINT, topNEntityName.get()));
+                } else if (recordName.isPresent()) {
+                    metricInfo.getLabels().add(new LabelValuePair(LabelName.RECORD, recordName.get()));
+                } else {
+                    checkLabels(labelMap, LabelName.ENDPOINT);
+                    metricInfo.getLabels()
+                              .add(new LabelValuePair(LabelName.ENDPOINT, labelMap.get(LabelName.ENDPOINT)));
+                }
+                break;
+        }
+
+        return metricInfo;
+    }
+
+    private Optional<ValueColumnMetadata.ValueColumn> getValueColumn(String metricName) {
+        return ValueColumnMetadata.INSTANCE.readValueColumnDefinition(metricName);
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLMatchVisitor.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLMatchVisitor.java
new file mode 100644
index 000000000000..815b6432e9e7
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/PromQLMatchVisitor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.promql.rt;
+
+import java.util.Map;
+import org.apache.skywalking.oap.query.promql.entity.ErrorType;
+import org.apache.skywalking.oap.query.promql.entity.LabelName;
+import org.apache.skywalking.oap.query.promql.rt.result.MatcherSetResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
+import org.apache.skywalking.promql.rt.grammar.PromQLParser;
+import org.apache.skywalking.promql.rt.grammar.PromQLParserBaseVisitor;
+
+public class PromQLMatchVisitor extends PromQLParserBaseVisitor<MatcherSetResult> {
+
+    @Override
+    public MatcherSetResult visitMetricInstant(PromQLParser.MetricInstantContext ctx) {
+        String metricName = ctx.metricName().getText();
+        MatcherSetResult result = new MatcherSetResult();
+        result.setResultType(ParseResultType.MATCH);
+        result.setMetricName(metricName);
+        Map<LabelName, String> labelMap = result.getLabelMap();
+        if (ctx.labelList() != null) {
+            for (PromQLParser.LabelContext labelCtx : ctx.labelList().label()) {
+                String labelName = labelCtx.labelName().getText();
+                String labelValue = labelCtx.labelValue().getText();
+                String labelValueTrim = labelValue.substring(1, labelValue.length() - 1);
+                try {
+                    labelMap.put(LabelName.labelOf(labelName), labelValueTrim);
+                } catch (IllegalArgumentException e) {
+                    result.setErrorType(ErrorType.BAD_DATA);
+                    result.setErrorInfo("Label:[" + labelName + "] is illegal.");
+                    return result;
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/IllegalExpressionException.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/IllegalExpressionException.java
new file mode 100644
index 000000000000..7313ced4ad2e
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/IllegalExpressionException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.promql.rt.exception;
+
+public class IllegalExpressionException extends Exception {
+    public IllegalExpressionException(String message) {
+        super(message);
+    }
+
+    public IllegalExpressionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
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
new file mode 100644
index 000000000000..9bab42e86fbd
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/exception/ParseErrorListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.promql.rt.exception;
+
+import graphql.org.antlr.v4.runtime.misc.ParseCancellationException;
+import org.antlr.v4.runtime.BaseErrorListener;
+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,
+                            int line,
+                            int charPositionInLine,
+                            String msg,
+                            RecognitionException e)
+        throws ParseCancellationException {
+
+        throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg);
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MatcherSetResult.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MatcherSetResult.java
new file mode 100644
index 000000000000..b2e8c240daf9
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MatcherSetResult.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.promql.rt.result;
+
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.LabelName;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MatcherSetResult extends ParseResult {
+    private String metricName;
+    private Map<LabelName, String> labelMap = new HashMap<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MetricsRangeResult.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MetricsRangeResult.java
new file mode 100644
index 000000000000..de0d3be72a46
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/MetricsRangeResult.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.promql.rt.result;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.MetricRangeData;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MetricsRangeResult extends ParseResult {
+    private List<MetricRangeData> metricDataList = new ArrayList<>();
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResult.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResult.java
new file mode 100644
index 000000000000..23a977deae58
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResult.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.promql.rt.result;
+
+import lombok.Data;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.apache.skywalking.oap.query.promql.entity.ErrorType;
+
+@Data
+public class ParseResult {
+    private ParseTree parseTree;
+    private ParseResultType resultType;
+    private boolean rangeExpression = false;
+    private ErrorType errorType;
+    private String errorInfo;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResultType.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResultType.java
new file mode 100644
index 000000000000..fcafc0389912
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ParseResultType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.promql.rt.result;
+
+public enum ParseResultType {
+    SCALAR,
+    METRICS_RANGE,
+    MATCH
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ScalarResult.java b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ScalarResult.java
new file mode 100644
index 000000000000..bb664e464c96
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/java/org/apache/skywalking/oap/query/promql/rt/result/ScalarResult.java
@@ -0,0 +1,30 @@
+/*
+ * 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.promql.rt.result;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.oap.query.promql.entity.MetricInfo;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ScalarResult extends ParseResult {
+    private MetricInfo metricInfo;
+    private double value;
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
new file mode 100644
index 000000000000..8978391a3619
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+org.apache.skywalking.oap.query.promql.PromQLModule
diff --git a/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 000000000000..de3fcac47395
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+org.apache.skywalking.oap.query.promql.PromQLProvider
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
index 36eb5de188a0..7830821962fd 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
@@ -83,8 +83,8 @@ private AggregationQueryService getQueryService() {
     private RecordQueryService getRecordQueryService() {
         if (recordQueryService == null) {
             this.recordQueryService = moduleManager.find(CoreModule.NAME)
-                                                        .provider()
-                                                        .getService(RecordQueryService.class);
+                                                   .provider()
+                                                   .getService(RecordQueryService.class);
         }
         return recordQueryService;
     }
@@ -119,7 +119,7 @@ public List<MetricDefinition> listMetrics(String regex) {
      * Read metrics single value in the duration of required metrics
      */
     public long readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName())) || !condition.getEntity().isValid()) {
+        if (!condition.senseScope() || !condition.getEntity().isValid()) {
             return 0;
         }
         return getMetricsQueryService().readMetricsValue(condition, duration);
@@ -129,7 +129,7 @@ public long readMetricsValue(MetricsCondition condition, Duration duration) thro
      * Read time-series values in the duration of required metrics
      */
     public MetricsValues readMetricsValues(MetricsCondition condition, Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName())) || !condition.getEntity().isValid()) {
+        if (!condition.senseScope() || !condition.getEntity().isValid()) {
             final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
             MetricsValues values = new MetricsValues();
             pointOfTimes.forEach(pointOfTime -> {
@@ -150,7 +150,7 @@ public MetricsValues readMetricsValues(MetricsCondition condition, Duration dura
      * Read entity list of required metrics and parent entity type.
      */
     public List<SelectedRecord> sortMetrics(TopNCondition condition, Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName()))) {
+        if (!condition.senseScope()) {
             return Collections.emptyList();
         }
         return getQueryService().sortMetrics(condition, duration);
@@ -164,7 +164,7 @@ public List<SelectedRecord> sortMetrics(TopNCondition condition, Duration durati
     public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
                                                         List<String> labels,
                                                         Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName())) || !condition.getEntity().isValid()) {
+        if (!condition.senseScope() || !condition.getEntity().isValid()) {
             final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
 
             List<MetricsValues> labeledValues = new ArrayList<>(labels.size());
@@ -200,7 +200,7 @@ public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
      * </pre>
      */
     public HeatMap readHeatMap(MetricsCondition condition, Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName())) || !condition.getEntity().isValid()) {
+        if (!condition.senseScope() || !condition.getEntity().isValid()) {
             DataTable emptyData = new DataTable();
             emptyData.put("0", 0L);
             final String rawdata = emptyData.toStorageData();
@@ -224,10 +224,11 @@ public HeatMap readHeatMap(MetricsCondition condition, Duration duration) throws
      */
     @Deprecated
     public List<SelectedRecord> readSampledRecords(TopNCondition condition, Duration duration) throws IOException {
-        if (MetricsType.UNKNOWN.equals(typeOfMetrics(condition.getName()))) {
+        RecordCondition recordCondition = new RecordCondition(condition);
+        if (!recordCondition.senseScope() || !recordCondition.getParentEntity().isValid()) {
             return Collections.emptyList();
         }
-        final List<Record> records = getRecordQueryService().readRecords(new RecordCondition(condition), duration);
+        final List<Record> records = getRecordQueryService().readRecords(recordCondition, duration);
         return records.stream().filter(Objects::nonNull).map(Record::toSelectedRecord).collect(Collectors.toList());
     }
 }
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/RecordsQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/RecordsQuery.java
index b0094e674b9f..608cc815f804 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/RecordsQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/RecordsQuery.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.query.graphql.resolver;
 
 import graphql.kickstart.tools.GraphQLQueryResolver;
+import java.util.Collections;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.query.RecordQueryService;
 import org.apache.skywalking.oap.server.core.query.input.Duration;
@@ -47,6 +48,9 @@ private RecordQueryService getRecordQueryService() {
     }
 
     public List<Record> readRecords(RecordCondition condition, Duration duration) throws IOException {
+        if (!condition.senseScope() || !condition.getParentEntity().isValid()) {
+            return Collections.emptyList();
+        }
         return getRecordQueryService().readRecords(condition, duration);
     }
-}
\ No newline at end of file
+}
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 a79526928efa..450328c6445d 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 +1,1 @@
-Subproject commit a79526928efafe2b6ce87d6a92104647660b6909
+Subproject commit 450328c6445d4dc50a868a0b797a14755f1d82cc
diff --git a/oap-server/server-starter/pom.xml b/oap-server/server-starter/pom.xml
index 28e11d16e54a..5d4bcce59b69 100644
--- a/oap-server/server-starter/pom.xml
+++ b/oap-server/server-starter/pom.xml
@@ -215,6 +215,11 @@
             <artifactId>query-graphql-plugin</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>promql-plugin</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <!-- queryBuild module -->
 
         <!-- alarm module -->
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 35163a15f1d0..9e73b3ce7f74 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -435,6 +435,17 @@ query-zipkin:
     # Default look back on the UI for search traces, 15 minutes in millis
     uiDefaultLookback: ${SW_QUERY_ZIPKIN_UI_DEFAULT_LOOKBACK:900000}
 
+promql:
+  selector: ${SW_PROMQL:default}
+  default:
+    # For HTTP server
+    restHost: ${SW_PROMQL_REST_HOST:0.0.0.0}
+    restPort: ${SW_PROMQL_REST_PORT:9099}
+    restContextPath: ${SW_PROMQL_REST_CONTEXT_PATH:/}
+    restMaxThreads: ${SW_PROMQL_REST_MAX_THREADS:200}
+    restIdleTimeOut: ${SW_PROMQL_REST_IDLE_TIMEOUT:30000}
+    restAcceptQueueSize: ${SW_PROMQL_REST_QUEUE_SIZE:0}
+
 alarm:
   selector: ${SW_ALARM:default}
   default:
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBRecordsQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBRecordsQueryDAO.java
index 0d150491336d..8bf59354bcee 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBRecordsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBRecordsQueryDAO.java
@@ -55,9 +55,7 @@ public List<Record> readRecords(RecordCondition condition, String valueColumnNam
                 timestampRange, new QueryBuilder<StreamQuery>() {
                     @Override
                     protected void apply(StreamQuery query) {
-                        if (condition.getParentEntity() != null && condition.getParentEntity().buildId() != null) {
-                            query.and(eq(TopN.ENTITY_ID, condition.getParentEntity().buildId()));
-                        }
+                        query.and(eq(TopN.ENTITY_ID, condition.getParentEntity().buildId()));
                         if (condition.getOrder() == Order.DES) {
                             query.setOrderBy(new StreamQuery.OrderBy(valueColumnName, AbstractQuery.Sort.DESC));
                         } else {
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/RecordsQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/RecordsQueryEsDAO.java
index a79caa10f12c..dc25175c2ced 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/RecordsQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/RecordsQueryEsDAO.java
@@ -58,10 +58,7 @@ public List<Record> readRecords(final RecordCondition condition,
         if (IndexController.LogicIndicesRegister.isMergedTable(condition.getName())) {
             query.must(Query.term(IndexController.LogicIndicesRegister.RECORD_TABLE_NAME, condition.getName()));
         }
-
-        if (condition.getParentEntity() != null && condition.getParentEntity().buildId() != null) {
-            query.must(Query.term(TopN.ENTITY_ID, condition.getParentEntity().buildId()));
-        }
+        query.must(Query.term(TopN.ENTITY_ID, condition.getParentEntity().buildId()));
 
         final SearchBuilder search =
             Search.builder()
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCRecordsQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCRecordsQueryDAO.java
index c7f2b0ea820b..1a445ac51fb5 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCRecordsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCRecordsQueryDAO.java
@@ -40,16 +40,12 @@ public class JDBCRecordsQueryDAO implements IRecordsQueryDAO {
 
     @Override
     public List<Record> readRecords(final RecordCondition condition,
-                                           final String valueColumnName,
-                                           final Duration duration) throws IOException {
+                                    final String valueColumnName,
+                                    final Duration duration) throws IOException {
         StringBuilder sql = new StringBuilder("select * from " + condition.getName() + " where ");
         List<Object> parameters = new ArrayList<>(10);
-
-        if (condition.getParentEntity() != null && condition.getParentEntity().buildId() != null) {
-            sql.append(" ").append(TopN.ENTITY_ID).append(" = ? and");
-            parameters.add(condition.getParentEntity().buildId());
-        }
-
+        sql.append(" ").append(TopN.ENTITY_ID).append(" = ? and");
+        parameters.add(condition.getParentEntity().buildId());
         sql.append(" ").append(TopN.TIME_BUCKET).append(" >= ?");
         parameters.add(duration.getStartTimeBucketInSec());
         sql.append(" and ").append(TopN.TIME_BUCKET).append(" <= ?");
diff --git a/pom.xml b/pom.xml
index 946cda7cafcf..020e21861332 100755
--- a/pom.xml
+++ b/pom.xml
@@ -177,7 +177,7 @@
           in a single place. Upgrading Antlr version here requires recompiling the oal-rt/oal-rt modules so users
           won't face error like "ANTLR Tool version 4.9.3 used for code generation does not match the current runtime version 4.7.1".
         -->
-        <antlr.version>4.9.2</antlr.version>
+        <antlr.version>4.11.1</antlr.version>
 
         <!-- Plugin versions -->
         <maven-failsafe-plugin.version>3.0.0-M8</maven-failsafe-plugin.version>


Test patch:
diff --git a/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
new file mode 100644
index 000000000000..0c1d31a0a242
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLExprQueryVisitorTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.promql.rt.parser;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import lombok.SneakyThrows;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.apache.skywalking.oap.query.graphql.resolver.MetricsQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.RecordsQuery;
+import org.apache.skywalking.oap.query.promql.entity.TimeValuePair;
+import org.apache.skywalking.oap.query.promql.handler.PromQLApiHandler;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
+import org.apache.skywalking.oap.query.promql.rt.result.MetricsRangeResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResult;
+import org.apache.skywalking.oap.query.promql.rt.PromQLExprQueryVisitor;
+import org.apache.skywalking.oap.query.promql.rt.result.ScalarResult;
+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.Step;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.sql.Function;
+import org.apache.skywalking.oap.server.core.query.type.KVInt;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
+import org.apache.skywalking.promql.rt.grammar.PromQLLexer;
+import org.apache.skywalking.promql.rt.grammar.PromQLParser;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+
+public class PromQLExprQueryVisitorTest {
+    private MetricsQuery metricsQuery;
+    private RecordsQuery recordsQuery;
+    private Duration duration;
+    private static final long TIME_2023022010 = DurationUtils.INSTANCE.parseToDateTime(
+                                                           Step.HOUR, 2023022010)
+                                                                .getMillis() / 1000;
+    private static final long TIME_2023022011 = DurationUtils.INSTANCE.parseToDateTime(
+                                                           Step.HOUR, 2023022011)
+                                                                .getMillis() / 1000;
+    private static final long TIME_2023022012 = DurationUtils.INSTANCE.parseToDateTime(
+                                                                Step.HOUR, 2023022012)
+                                                                     .getMillis() / 1000;
+
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+            {
+                "ScalarBinaryOp",
+                PromQLApiHandler.QueryType.RANGE,
+                "200 / 2 - 2 * 6 + 2 * 6",
+                ParseResultType.SCALAR,
+                "100"
+            },
+            {
+                "ScalarCompareOp",
+                PromQLApiHandler.QueryType.RANGE,
+                "2 > bool 1",
+                ParseResultType.SCALAR,
+                "1"
+            },
+            {
+                "Metrics",
+                PromQLApiHandler.QueryType.RANGE,
+                "service_cpm{service='serviceA', layer='GENERAL'}",
+                ParseResultType.METRICS_RANGE,
+                List.of(new TimeValuePair(TIME_2023022010, "0"), new TimeValuePair(TIME_2023022011, "1"),
+                        new TimeValuePair(TIME_2023022012, "2"))
+            },
+            {
+                "MetricsScalarBinaryOp",
+                PromQLApiHandler.QueryType.RANGE,
+                "service_cpm{service='serviceA', layer='GENERAL'} + 100",
+                ParseResultType.METRICS_RANGE,
+                List.of(new TimeValuePair(TIME_2023022010, "100"), new TimeValuePair(TIME_2023022011, "101"),
+                        new TimeValuePair(TIME_2023022012, "102"))
+            },
+            {
+                "MetricsBinaryOp",
+                PromQLApiHandler.QueryType.RANGE,
+                "service_cpm{service='serviceA', layer='GENERAL'} + service_cpm{service='serviceA', layer='GENERAL'}",
+                ParseResultType.METRICS_RANGE,
+                List.of(new TimeValuePair(TIME_2023022010, "0"), new TimeValuePair(TIME_2023022011, "2"),
+                        new TimeValuePair(TIME_2023022012, "4"))
+            },
+            {
+                "MetricsScalarCompareOp",
+                PromQLApiHandler.QueryType.RANGE,
+                "service_cpm{service='serviceA', layer='GENERAL'} > 1",
+                ParseResultType.METRICS_RANGE,
+                List.of(new TimeValuePair(TIME_2023022012, "2"))
+            }
+        });
+    }
+
+    @SneakyThrows
+    @BeforeEach
+    public void setup() {
+        ValueColumnMetadata.INSTANCE.putIfAbsent("service_cpm", "value", Column.ValueDataType.COMMON_VALUE,
+                                                 Function.Avg, 0,
+                                                 DefaultScopeDefine.SERVICE
+        );
+        metricsQuery = mock(MetricsQuery.class);
+        recordsQuery = mock(RecordsQuery.class);
+        duration = new Duration();
+        duration.setStep(Step.HOUR);
+        duration.setStart("2023-02-20 10");
+        duration.setEnd("2023-02-20 12");
+        Mockito.doReturn(mockMetricsValues())
+               .when(metricsQuery)
+               .readMetricsValues(any(MetricsCondition.class), any(Duration.class));
+    }
+
+    private MetricsValues mockMetricsValues() {
+        final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
+        MetricsValues values = new MetricsValues();
+        for (int i = 0; i < pointOfTimes.size(); i++) {
+            final KVInt kvInt = new KVInt();
+            kvInt.setId(String.valueOf(pointOfTimes.get(i).getPoint()));
+            kvInt.setValue(i);
+            values.getValues().addKVInt(kvInt);
+        }
+        return values;
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("data")
+    public void test(String name,
+                     PromQLApiHandler.QueryType queryType,
+                     String expression,
+                     ParseResultType wantType,
+                     Object wantResultValues) {
+        PromQLLexer lexer = new PromQLLexer(CharStreams.fromString(expression));
+        CommonTokenStream tokens = new CommonTokenStream(lexer);
+        PromQLParser parser = new PromQLParser(tokens);
+        ParseTree tree = parser.expression();
+        PromQLExprQueryVisitor visitor = new PromQLExprQueryVisitor(metricsQuery, recordsQuery, duration, queryType);
+        ParseResult parseResult = visitor.visit(tree);
+        Assertions.assertEquals(wantType, parseResult.getResultType());
+        switch (parseResult.getResultType()) {
+            case SCALAR:
+                ScalarResult scalarResult = (ScalarResult) parseResult;
+                Assertions.assertEquals(
+                    Double.parseDouble(String.valueOf(wantResultValues)),
+                    scalarResult.getValue()
+                );
+                break;
+            case METRICS_RANGE:
+                MetricsRangeResult metricsRangeResult = (MetricsRangeResult) parseResult;
+                Assertions.assertEquals(metricsRangeResult.getMetricDataList().get(0).getValues(), wantResultValues);
+                break;
+            default:
+                Assertions.fail();
+        }
+    }
+}
diff --git a/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLMatchVisitorTest.java b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLMatchVisitorTest.java
new file mode 100644
index 000000000000..e4becc6643d2
--- /dev/null
+++ b/oap-server/server-query-plugin/promql-plugin/src/test/java/org/apache/skywalking/promql/rt/parser/PromQLMatchVisitorTest.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.promql.rt.parser;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.apache.skywalking.oap.query.promql.rt.PromQLMatchVisitor;
+import org.apache.skywalking.oap.query.promql.rt.result.MatcherSetResult;
+import org.apache.skywalking.oap.query.promql.rt.result.ParseResultType;
+import org.apache.skywalking.promql.rt.grammar.PromQLLexer;
+import org.apache.skywalking.promql.rt.grammar.PromQLParser;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PromQLMatchVisitorTest {
+    @Test
+    public void testMatchVisitor() {
+        PromQLLexer lexer = new PromQLLexer(
+            CharStreams.fromString("service_cpm{service='serviceA', layer='GENERAL'}"));
+        CommonTokenStream tokens = new CommonTokenStream(lexer);
+        PromQLParser parser = new PromQLParser(tokens);
+        ParseTree tree = parser.expression();
+        PromQLMatchVisitor visitor = new PromQLMatchVisitor();
+        MatcherSetResult parseResult = visitor.visit(tree);
+        Assertions.assertEquals(ParseResultType.MATCH, parseResult.getResultType());
+        Assertions.assertEquals("service_cpm", parseResult.getMetricName());
+        Assertions.assertEquals(2, parseResult.getLabelMap().size());
+    }
+}
diff --git a/test/e2e-v2/cases/mariadb/mariadb-cases.yaml b/test/e2e-v2/cases/mariadb/mariadb-cases.yaml
index a3ba087158d6..8376ee9841f0 100644
--- a/test/e2e-v2/cases/mariadb/mariadb-cases.yaml
+++ b/test/e2e-v2/cases/mariadb/mariadb-cases.yaml
@@ -79,5 +79,5 @@ cases:
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=meter_mysql_instance_connects_available --service-name=mysql::db --instance-name=mariadb-service:9104 |yq e 'to_entries' -
     expected:  expected/metrics-has-value.yml
   # slow sql
-  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
-    expected:  expected/db-has-value.yml
\ No newline at end of file
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement --service-name=mysql::db |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
+    expected:  expected/db-has-value.yml
diff --git a/test/e2e-v2/cases/mysql/mysql-cases.yaml b/test/e2e-v2/cases/mysql/mysql-cases.yaml
index bb59d32dcda2..c327890c5e8c 100644
--- a/test/e2e-v2/cases/mysql/mysql-cases.yaml
+++ b/test/e2e-v2/cases/mysql/mysql-cases.yaml
@@ -79,5 +79,5 @@ cases:
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=meter_mysql_instance_connects_available --service-name=mysql::root[root] --instance-name=mysql-service:9104 |yq e 'to_entries' -
     expected:  expected/metrics-has-value.yml
   # slow sql
-  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
-    expected:  expected/db-has-value.yml
\ No newline at end of file
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement --service-name=mysql::root[root] |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
+    expected:  expected/db-has-value.yml
diff --git a/test/e2e-v2/cases/postgresql/postgresql-cases.yaml b/test/e2e-v2/cases/postgresql/postgresql-cases.yaml
index 18a5e25fe2b7..7d9de5e337d6 100644
--- a/test/e2e-v2/cases/postgresql/postgresql-cases.yaml
+++ b/test/e2e-v2/cases/postgresql/postgresql-cases.yaml
@@ -73,5 +73,5 @@ cases:
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics multiple-linear --name=meter_pg_instance_active_sessions --service-name=postgresql::postgres:5432 --instance-name=postgres-exporter:9187 |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
     expected: expected/metrics-has-value-label.yml
   # slow sql
-  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql records list --name=top_n_database_statement --service-name=postgresql::postgres:5432 |yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
     expected:  expected/db-has-value.yml
diff --git a/test/e2e-v2/cases/virtual-mq/docker-compose.yml b/test/e2e-v2/cases/virtual-mq/docker-compose.yml
index 38f9d68a00ba..b13269431cec 100644
--- a/test/e2e-v2/cases/virtual-mq/docker-compose.yml
+++ b/test/e2e-v2/cases/virtual-mq/docker-compose.yml
@@ -17,8 +17,6 @@ version: '2.1'
 
 services:
   oap:
-    environment:
-      SW_METER_ANALYZER_ACTIVE_FILES:
     extends:
       file: ../../script/docker-compose/base-compose.yml
       service: oap
diff --git a/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value0.yml b/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value0.yml
index ce1f3d83c82f..f7092ba3fac5 100644
--- a/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value0.yml
+++ b/test/e2e-v2/cases/virtual-mq/expected/metrics-has-value0.yml
@@ -15,5 +15,5 @@
 
 {{- contains . }}
 - key: {{ notEmpty .key }}
-  value: {{ ge .value 0 }}
+  value: {{ gt .value 0 }}
 {{- end }}