This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 9149de2  Support multiple linear values and merging p50/75/90/95/99 
into percentile (#4214)
9149de2 is described below

commit 9149de2a6e686ff5442ec5bfd2d00ea4912fb911
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Sun Jan 12 22:10:50 2020 +0800

    Support multiple linear values and merging p50/75/90/95/99 into percentile 
(#4214)
    
    * Support new percentile func with new alarm and exporter for this new func.
    
    * Fix e2e and OAL script
    
    * Fix wrong column.
    
    * Fix percentile bug and oal engine bug.
    
    * Update query protocol and add percentile test case
    
    * Support new query
    
    * Adopt GraphQL requirement
    
    * Fix wrong type cast.
    
    * Fix query in H2 and ES.
    
    * Fix docs and comments.
    
    * Fix an e2e compile issue
    
    * Fix javadoc issue and e2e test issue.
    
    * Change CPM to Apdex in TTL test.
    
    * Fix OAL for TTL e2e
    
    * Add metrics query for service percentile.
    
    * Fix OAL engine bug. Method deserialize is not working when more than two 
field types are IntKeyLongValueHashMap
    
    * Support multiple IntKeyLongValueHashMap fields in remote. About 
serialize/deserialize methods.
    
    * Fix graphql statement error in e2e.
    
    * Fix serialize not working and add generated serialize/deserialize of 
percentile into test cases.
    
    * Fix test case format
    
    * Remove generated code test.
    
    * Fix failed e2e test
    
    * Use avg resp time to apdex in the TTL test.
    
    * ADD multiple linear metrics check for endpoint in e2e cluster.
    
    * Support `-` to represent no threshold and doc of alarm about this.
    
    * Move break to right place.
    
    * Fix wrong break(s)
    
    * Fix break and add a test case for multiple values alarm.
    
    * Fix format.
    
    * Add more doc for this new feature and GraphQL query protocol.
    
    Co-authored-by: Jared Tan <[email protected]>
    Co-authored-by: kezhenxu94 <[email protected]>
---
 .github/workflows/e2e.yaml                         |   2 +-
 dist-material/alarm-settings.yml                   |   8 +-
 docs/en/concepts-and-designs/oal.md                |  20 ++--
 docs/en/protocols/README.md                        |  77 +------------
 docs/en/protocols/query-protocol.md                | 103 +++++++++++++++++
 docs/en/setup/backend/backend-alarm.md             |  19 ++-
 docs/en/setup/backend/metrics-exporter.md          |   2 +
 docs/en/setup/backend/ttl.md                       |   2 +-
 .../exporter/provider/grpc/GRPCExporter.java       |   6 +
 .../exporter/src/main/proto/metric-exporter.proto  |   2 +
 .../code-templates/metrics/deserialize.ftl         |   7 +-
 .../resources/code-templates/metrics/serialize.ftl |   8 +-
 .../core/alarm/provider/MetricsValueType.java      |   2 +-
 .../server/core/alarm/provider/RunningRule.java    |  67 ++++++++---
 .../oap/server/core/alarm/provider/Threshold.java  |  25 +++-
 .../core/alarm/provider/RunningRuleTest.java       |  92 +++++++++++++++
 .../server/core/alarm/provider/ThresholdTest.java  |  11 +-
 .../src/main/resources/official_analysis.oal       |  49 ++------
 .../analysis/metrics/MultiIntValuesHolder.java}    |  11 +-
 .../core/analysis/metrics/PercentileMetrics.java   | 120 +++++++++++++++++++
 .../oap/server/core/query/MetricQueryService.java  |  27 ++++-
 .../oap/server/core/query/entity/IntValues.java    |   9 +-
 .../core/storage/annotation/ValueColumnIds.java    |  16 ++-
 .../core/storage/query/IMetricsQueryDAO.java       |   2 +
 .../server-core/src/main/proto/RemoteService.proto |   6 +-
 .../analysis/metrics/PercentileMetricsTest.java    | 127 +++++++++++++++++++++
 .../oap/query/graphql/resolver/MetricQuery.java    |   8 ++
 .../src/main/resources/query-protocol              |   2 +-
 .../elasticsearch/query/MetricsQueryEsDAO.java     |  65 +++++++++--
 .../plugin/jdbc/h2/dao/H2MetricsQueryDAO.java      |  87 ++++++++++++--
 .../apache/skywalking/e2e/SimpleQueryClient.java   |  26 +++++
 .../apache/skywalking/e2e/metrics/MetricsData.java |  20 +---
 .../skywalking/e2e/metrics/MetricsMatcher.java     |  55 +++++++--
 .../skywalking/e2e/metrics/MetricsQuery.java       |  40 +++----
 .../skywalking/e2e/metrics/MultiMetricsData.java   |  16 ++-
 .../src/main/resources/metrics-multiLines.gql      |  37 ++++++
 .../skywalking/e2e/ClusterVerificationITCase.java  |   8 ++
 .../src/docker/ttl_official_analysis.oal           |   2 +-
 .../apache/skywalking/e2e/StorageTTLITCase.java    |   4 +-
 test/e2e/run.sh                                    |   2 +-
 40 files changed, 948 insertions(+), 244 deletions(-)

diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 0a1b28e..2a35a9e 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -64,7 +64,7 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-maven-
       - name: Set environment
-        run: export MAVEN_OPTS='-Dmaven.repo.local=~/.m2/repository 
-XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:+CMSClassUnloadingEnabled 
-XX:+UseConcMarkSweepGC -XX:-UseGCOverheadLimit -Xmx3g'
+        run: export MAVEN_OPTS='-XX:+TieredCompilation -XX:TieredStopAtLevel=1 
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:-UseGCOverheadLimit 
-Xmx3g'
       - name: Compile & Install Test Codes
         run: |
           ./mvnw checkstyle:check apache-rat:check
diff --git a/dist-material/alarm-settings.yml b/dist-material/alarm-settings.yml
index 8ed9c2e..fc5e121 100644
--- a/dist-material/alarm-settings.yml
+++ b/dist-material/alarm-settings.yml
@@ -37,15 +37,15 @@ rules:
     # How many times of checks, the alarm keeps silence after alarm triggered, 
default as same as period.
     silence-period: 3
     message: Successful rate of service {name} is lower than 80% in 2 minutes 
of last 10 minutes
-  service_p90_sla_rule:
+  service_resp_time_percentile_rule:
     # Metrics value need to be long, double or int
-    metrics-name: service_p90
+    metrics-name: service_percentile
     op: ">"
-    threshold: 1000
+    threshold: 1000,1000,1000,1000,1000
     period: 10
     count: 3
     silence-period: 5
-    message: 90% response time of service {name} is more than 1000ms in 3 
minutes of last 10 minutes
+    message: Percentile response time of service {name} alarm in 3 minutes of 
last 10 minutes, due to more than one condition of p50 > 1000, p75 > 1000, p90 
> 1000, p95 > 1000, p99 > 1000
   service_instance_resp_time_rule:
     metrics-name: service_instance_resp_time
     op: ">"
diff --git a/docs/en/concepts-and-designs/oal.md 
b/docs/en/concepts-and-designs/oal.md
index b132115..ec3129a 100644
--- a/docs/en/concepts-and-designs/oal.md
+++ b/docs/en/concepts-and-designs/oal.md
@@ -57,11 +57,8 @@ In this case, all input are requests of each endpoint, 
condition is `endpoint.st
 > Service_Calls_Sum = from(Service.*).sum();
 
 In this case, calls of each service. 
-- `p99`, `p95`, `p90`, `p75`, `p50`. Read [p99 in 
WIKI](https://en.wikipedia.org/wiki/Percentile)
-> All_p99 = from(All.latency).p99(10);
 
-In this case, p99 value of all incoming requests. The parameter is the 
precision of p99 latency calculation, such as in above case, 120ms and 124 are 
considered same.
-- `thermodynamic`. Read [Heatmap in 
WIKI](https://en.wikipedia.org/wiki/Heat_map))
+- `thermodynamic`. Read [Heatmap in 
WIKI](https://en.wikipedia.org/wiki/Heat_map)
 > All_heatmap = from(All.latency).thermodynamic(100, 20);
 
 In this case, thermodynamic heatmap of all incoming requests. 
@@ -75,6 +72,16 @@ In this case, apdex score of each service.
 The parameter (1) is the service name, which effects the Apdex threshold value 
loaded from service-apdex-threshold.yml in the config folder.
 The parameter (2) is the status of this request. The status(success/failure) 
effects the Apdex calculation.
 
+- `p99`, `p95`, `p90`, `p75`, `p50`. Read [percentile in 
WIKI](https://en.wikipedia.org/wiki/Percentile)
+> all_percentile = from(All.latency).percentile(10);
+
+**percentile** is the first multiple value metrics, introduced since 7.0.0. As 
having multiple values, it could be query through `getMultipleLinearIntValues` 
GraphQL query.
+In this case, `p99`, `p95`, `p90`, `p75`, `p50` of all incoming request. The 
parameter is the precision of p99 latency calculation, such as in above case, 
120ms and 124 are considered same.
+Before 7.0.0, use `p99`, `p95`, `p90`, `p75`, `p50` func(s) to calculate 
metrics separately. Still supported in 7.x, but don't be recommended, and don't 
be included in official OAL script. 
+> All_p99 = from(All.latency).p99(10);
+
+In this case, p99 value of all incoming requests. The parameter is the 
precision of p99 latency calculation, such as in above case, 120ms and 124 are 
considered same.
+
 ## Metrics name
 The metrics name for storage implementor, alarm and query modules. The type 
inference supported by core.
 
@@ -101,9 +108,8 @@ serv_Endpoint_p99 = from(Endpoint.latency).filter(name like 
("serv%")).summary(0
 // Caculate the avg response time of each Endpoint
 Endpoint_avg = from(Endpoint.latency).avg()
 
-// Caculate the histogram of each Endpoint by 50 ms steps.
-// Always thermodynamic diagram in UI matches this metrics. 
-Endpoint_histogram = from(Endpoint.latency).histogram(50)
+// Caculate the p50, p75, p90, p95 and p99 of each Endpoint by 50 ms steps.
+Endpoint_percentile = from(Endpoint.latency).percentile(10)
 
 // Caculate the percent of response status is true, for each service.
 Endpoint_success = from(Endpoint.*).filter(status = "true").percent()
diff --git a/docs/en/protocols/README.md b/docs/en/protocols/README.md
index 82e9653..b9bc32a 100644
--- a/docs/en/protocols/README.md
+++ b/docs/en/protocols/README.md
@@ -61,79 +61,4 @@ Backend is based on modularization principle, so very easy 
to extend a new recei
 
 ## Query Protocol
 Query protocol follows GraphQL grammar, provides data query capabilities, 
which depends on your analysis metrics.
-
-There are 5 dimensionality data is provided.
-1. Metadata. Metadata includes the brief info of the whole under monitoring 
services and their instances, endpoints, etc.
-Use multiple ways to query this meta data.
-1. Topology. Show the topology and dependency graph of services or endpoints. 
Including direct relationship or global map.
-1. Metrics. Metrics query targets all the objects defined in [OAL 
script](../concepts-and-designs/oal.md). You could get the 
-metrics data in linear or thermodynamic matrix formats based on the 
aggregation functions in script. 
-1. Aggregation. Aggregation query means the metrics data need a secondary 
aggregation in query stage, which makes the query 
-interfaces have some different arguments. Such as, `TopN` list of services is 
a very typical aggregation query, 
-metrics stream aggregation just calculates the metrics values of each service, 
but the expected list needs ordering metrics data
-by the values.
-1. Trace. Query distributed traces by this.
-1. Alarm. Through alarm query, you can have alarm trend and details.
-
-The actual query GraphQL scrips could be found inside `query-protocol` folder 
in 
[here](../../../oap-server/server-query-plugin/query-graphql-plugin/src/main/resources).
-
-Here is the list of all existing metrics names, based on 
[official_analysis.oal](../../../oap-server/server-bootstrap/src/main/resources/official_analysis.oal)
-
-**Global metrics**
-- all_p99, p99 response time of all services
-- all_p95
-- all_p90
-- all_p75
-- all_p70
-- all_heatmap, the response time heatmap of all services 
-
-**Service metrics**
-- service_resp_time, avg response time of service
-- service_sla, successful rate of service
-- service_cpm, calls per minute of service
-- service_p99, p99 response time of service
-- service_p95
-- service_p90
-- service_p75
-- service_p50
-
-**Service instance metrics**
-- service_instance_sla, successful rate of service instance
-- service_instance_resp_time, avg response time of service instance
-- service_instance_cpm, calls per minute of service instance
-
-**Endpoint metrics**
-- endpoint_cpm, calls per minute of endpoint
-- endpoint_avg, avg response time of endpoint
-- endpoint_sla, successful rate of endpoint
-- endpoint_p99, p99 response time of endpoint
-- endpoint_p95
-- endpoint_p90
-- endpoint_p75
-- endpoint_p50
-
-**JVM metrics**, JVM related metrics, only work when javaagent is active
-- instance_jvm_cpu
-- instance_jvm_memory_heap
-- instance_jvm_memory_noheap
-- instance_jvm_memory_heap_max
-- instance_jvm_memory_noheap_max
-- instance_jvm_young_gc_time
-- instance_jvm_old_gc_time
-- instance_jvm_young_gc_count
-- instance_jvm_old_gc_count
-
-**Service relation metrics**, represents the metrics of calls between service. 
-The metrics ID could be
-got in topology query only.
-- service_relation_client_cpm, calls per minute detected at client side
-- service_relation_server_cpm, calls per minute detected at server side
-- service_relation_client_call_sla, successful rate detected at client side
-- service_relation_server_call_sla, successful rate detected at server side
-- service_relation_client_resp_time, avg response time detected at client side
-- service_relation_server_resp_time, avg response time detected at server side
-
-**Endpoint relation metrics**, represents the metrics between dependency 
endpoints. Only work when tracing agent.
-The metrics ID could be got in topology query only.
-- endpoint_relation_cpm
-- endpoint_relation_resp_time
+Read [query protocol doc](query-protocol.md) for more details.
diff --git a/docs/en/protocols/query-protocol.md 
b/docs/en/protocols/query-protocol.md
new file mode 100644
index 0000000..4beefb4
--- /dev/null
+++ b/docs/en/protocols/query-protocol.md
@@ -0,0 +1,103 @@
+# Query Protocol
+Query Protocol defines a set of APIs in GraphQL grammar to provide data query 
and interactive capabilities with SkyWalking
+native visualization tool or 3rd party system, including Web UI, CLI or 
private system.
+
+Query protocol official repository, 
https://github.com/apache/skywalking-query-protocol.
+
+### Metadata  
+Metadata includes the brief info of the whole under monitoring services and 
their instances, endpoints, etc.
+Use multiple ways to query this meta data.
+```graphql
+extend type Query {
+    getGlobalBrief(duration: Duration!): ClusterBrief
+
+    # Normal service related metainfo 
+    getAllServices(duration: Duration!): [Service!]!
+    searchServices(duration: Duration!, keyword: String!): [Service!]!
+    searchService(serviceCode: String!): Service
+    
+    # Fetch all services of Browser type
+    getAllBrowserServices(duration: Duration!): [Service!]!
+
+    # Service intance query
+    getServiceInstances(duration: Duration!, serviceId: ID!): 
[ServiceInstance!]!
+
+    # Endpoint query
+    # Consider there are huge numbers of endpoint,
+    # must use endpoint owner's service id, keyword and limit filter to do 
query.
+    searchEndpoint(keyword: String!, serviceId: ID!, limit: Int!): [Endpoint!]!
+    getEndpointInfo(endpointId: ID!): EndpointInfo
+
+    # Database related meta info.
+    getAllDatabases(duration: Duration!): [Database!]!
+    getTimeInfo: TimeInfo
+}
+```
+
+### Topology
+Show the topology and dependency graph of services or endpoints. Including 
direct relationship or global map.
+
+```graphql
+extend type Query {
+    # Query the global topology
+    getGlobalTopology(duration: Duration!): Topology
+    # Query the topology, based on the given service
+    getServiceTopology(serviceId: ID!, duration: Duration!): Topology
+    # Query the instance topology, based on the given clientServiceId and 
serverServiceId
+    getServiceInstanceTopology(clientServiceId: ID!, serverServiceId: ID!, 
duration: Duration!): ServiceInstanceTopology
+    # Query the topology, based on the given endpoint
+    getEndpointTopology(endpointId: ID!, duration: Duration!): Topology
+}
+```
+
+### Metrics
+Metrics query targets all the objects defined in [OAL 
script](../concepts-and-designs/oal.md). You could get the 
+metrics data in linear or thermodynamic matrix formats based on the 
aggregation functions in script. 
+
+3 types of metrics could be query
+1. Single value. The type of most default metrics is single value, consider 
this as default. `getValues` and `getLinearIntValues` are suitable for this.
+1. Multiple value.  One metrics defined in OAL include multiple value 
calculations. Use `getMultipleLinearIntValues` to get all values. `percentile` 
is a typical multiple value func in OAL.
+1. Heatmap value. Read [Heatmap in 
WIKI](https://en.wikipedia.org/wiki/Heat_map) for detail. `thermodynamic` is 
the only OAL func. Use `getThermodynamic` to get the values.
+```graphql
+extend type Query {
+    getValues(metric: BatchMetricConditions!, duration: Duration!): IntValues
+    getLinearIntValues(metric: MetricCondition!, duration: Duration!): 
IntValues
+    # Query the type of metrics including multiple values, and format them as 
multiple linears.
+    # The seq of these multiple lines base on the calculation func in OAL
+    # Such as, should us this to query the result of func 
percentile(50,75,90,95,99) in OAL,
+    # then five lines will be responsed, p50 is the first element of return 
value.
+    getMultipleLinearIntValues(metric: MetricCondition!, numOfLinear: Int!, 
duration: Duration!): [IntValues!]!
+    getThermodynamic(metric: MetricCondition!, duration: Duration!): 
Thermodynamic
+}
+```
+
+Metrics are defined in the 
[official_analysis.oal](../../../oap-server/server-bootstrap/src/main/resources/official_analysis.oal).
+
+### Aggregation
+Aggregation query means the metrics data need a secondary aggregation in query 
stage, which makes the query 
+interfaces have some different arguments. Such as, `TopN` list of services is 
a very typical aggregation query, 
+metrics stream aggregation just calculates the metrics values of each service, 
but the expected list needs ordering metrics data
+by the values.
+
+Aggregation query is for single value metrics only.
+
+```graphql
+# The aggregation query is different with the metric query.
+# All aggregation queries require backend or/and storage do aggregation in 
query time.
+extend type Query {
+    # TopN is an aggregation query.
+    getServiceTopN(name: String!, topN: Int!, duration: Duration!, order: 
Order!): [TopNEntity!]!
+    getAllServiceInstanceTopN(name: String!, topN: Int!, duration: Duration!, 
order: Order!): [TopNEntity!]!
+    getServiceInstanceTopN(serviceId: ID!, name: String!, topN: Int!, 
duration: Duration!, order: Order!): [TopNEntity!]!
+    getAllEndpointTopN(name: String!, topN: Int!, duration: Duration!, order: 
Order!): [TopNEntity!]!
+    getEndpointTopN(serviceId: ID!, name: String!, topN: Int!, duration: 
Duration!, order: Order!): [TopNEntity!]!
+}
+```
+
+### Others
+The following query(s) are for specific features, including trace, alarm or 
profile.
+1. Trace. Query distributed traces by this.
+1. Alarm. Through alarm query, you can have alarm trend and details.
+
+The actual query GraphQL scrips could be found inside `query-protocol` folder 
in 
[here](../../../oap-server/server-query-plugin/query-graphql-plugin/src/main/resources).
+
diff --git a/docs/en/setup/backend/backend-alarm.md 
b/docs/en/setup/backend/backend-alarm.md
index 95a4cdc..e13d915 100644
--- a/docs/en/setup/backend/backend-alarm.md
+++ b/docs/en/setup/backend/backend-alarm.md
@@ -13,7 +13,10 @@ Alarm rule is constituted by following keys
 endpoint name.
 - **Exclude names**. The following entity names are excluded in this rule. 
Such as Service name,
   endpoint name.
-- **Threshold**. The target value.
+- **Threshold**. The target value. 
+For multiple values metrics, such as **percentile**, the threshold is an 
array. Described like  `value1, value2, value3, value4, value5`.
+Each value could the threshold for each value of the metrics. Set the value to 
`-` if don't want to trigger alarm by this or some of the values.  
+Such as in **percentile**, `value1` is threshold of P50, and `-, -, value3, 
value4, value5` means, there is no threshold for P50 and P75 in percentile 
alarm rule.
 - **OP**. Operator, support `>`, `<`, `=`. Welcome to contribute all OPs.
 - **Period**. How long should the alarm rule should be checked. This is a time 
window, which goes with the
 backend deployment env time.
@@ -38,7 +41,6 @@ rules:
     count: 3
     # How many times of checks, the alarm keeps silence after alarm triggered, 
default as same as period.
     silence-period: 10
-    
   service_percent_rule:
     metrics-name: service_percent
     # [Optional] Default, match all services in this metrics
@@ -47,17 +49,28 @@ rules:
       - service_b
     exclude-names:
       - service_c
+    # Single value metrics threshold.
     threshold: 85
     op: <
     period: 10
     count: 4
+  service_resp_time_percentile_rule:
+    # Metrics value need to be long, double or int
+    metrics-name: service_percentile
+    op: ">"
+    # Multiple value metrics threshold. Thresholds for P50, P75, P90, P95, P99.
+    threshold: 1000,1000,1000,1000,1000
+    period: 10
+    count: 3
+    silence-period: 5
+    message: Percentile response time of service {name} alarm in 3 minutes of 
last 10 minutes, due to more than one condition of p50 > 1000, p75 > 1000, p90 
> 1000, p95 > 1000, p99 > 1000
 ```
 
 ### Default alarm rules
 We provided a default `alarm-setting.yml` in our distribution only for 
convenience, which including following rules
 1. Service average response time over 1s in last 3 minutes.
 1. Service success rate lower than 80% in last 2 minutes.
-1. Service 90% response time is over 1s in last 3 minutes
+1. Percentile of service response time is over 1s in last 3 minutes
 1. Service Instance average response time over 1s in last 2 minutes.
 1. Endpoint average response time over 1s in last 2 minutes.
 
diff --git a/docs/en/setup/backend/metrics-exporter.md 
b/docs/en/setup/backend/metrics-exporter.md
index 9ffe337..b190517 100644
--- a/docs/en/setup/backend/metrics-exporter.md
+++ b/docs/en/setup/backend/metrics-exporter.md
@@ -27,6 +27,7 @@ message ExportMetricValue {
     int64 timeBucket = 5;
     int64 longValue = 6;
     double doubleValue = 7;
+    repeated int64 longValues = 8;
 }
 
 message SubscriptionsResp {
@@ -36,6 +37,7 @@ message SubscriptionsResp {
 enum ValueType {
     LONG = 0;
     DOUBLE = 1;
+    MULTI_LONG = 2;
 }
 
 message SubscriptionReq {
diff --git a/docs/en/setup/backend/ttl.md b/docs/en/setup/backend/ttl.md
index 7a4ed50..b63ed75 100644
--- a/docs/en/setup/backend/ttl.md
+++ b/docs/en/setup/backend/ttl.md
@@ -1,7 +1,7 @@
 # TTL
 In SkyWalking, there are two types of observability data, besides metadata.
 1. Record, including trace and alarm. Maybe log in the future.
-1. Metric, including such as p99/p95/p90/p75/p50, heatmap, success rate, 
cpm(rpm) etc.
+1. Metric, including such as percentile, heatmap, success rate, cpm(rpm) etc.
 Metric is separated in minute/hour/day/month dimensions in storage, different 
indexes or tables.
 
 You have following settings for different types.
diff --git 
a/oap-server/exporter/src/main/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporter.java
 
b/oap-server/exporter/src/main/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporter.java
index 136c2aa..3114d6b 100644
--- 
a/oap-server/exporter/src/main/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporter.java
+++ 
b/oap-server/exporter/src/main/java/org/apache/skywalking/oap/server/exporter/provider/grpc/GRPCExporter.java
@@ -117,6 +117,12 @@ public class GRPCExporter extends MetricFormatter 
implements MetricValuesExportS
                 double value = ((DoubleValueHolder)metrics).getValue();
                 builder.setDoubleValue(value);
                 builder.setType(ValueType.DOUBLE);
+            } else if (metrics instanceof MultiIntValuesHolder) {
+                int[] values = ((MultiIntValuesHolder)metrics).getValues();
+                for (int value : values) {
+                    builder.addLongValues(value);
+                }
+                builder.setType(ValueType.MULTI_LONG);
             } else {
                 return;
             }
diff --git a/oap-server/exporter/src/main/proto/metric-exporter.proto 
b/oap-server/exporter/src/main/proto/metric-exporter.proto
index a612b28..3ade1e3 100644
--- a/oap-server/exporter/src/main/proto/metric-exporter.proto
+++ b/oap-server/exporter/src/main/proto/metric-exporter.proto
@@ -38,6 +38,7 @@ message ExportMetricValue {
     int64 timeBucket = 5;
     int64 longValue = 6;
     double doubleValue = 7;
+    repeated int64 longValues = 8;
 }
 
 message SubscriptionsResp {
@@ -47,6 +48,7 @@ message SubscriptionsResp {
 enum ValueType {
     LONG = 0;
     DOUBLE = 1;
+    MULTI_LONG = 2;
 }
 
 message SubscriptionReq {
diff --git 
a/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl 
b/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
index faf7e17..c66ec50 100644
--- 
a/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
+++ 
b/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
@@ -15,13 +15,12 @@ public void 
deserialize(org.apache.skywalking.oap.server.core.remote.grpc.proto.
         ${field.setter}(remoteData.getDataIntegers(${field?index}));
     </#list>
 
+    java.util.Iterator iterator;
     <#list serializeFields.intKeyLongValueHashMapFields as field>
-        setDetailGroup(new 
org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap(30));
-
-        java.util.Iterator iterator = 
remoteData.getDataIntLongPairListList().iterator();
+        iterator = 
remoteData.getDataLists(${field?index}).getValueList().iterator();
         while (iterator.hasNext()) {
             
org.apache.skywalking.oap.server.core.remote.grpc.proto.IntKeyLongValuePair 
element = 
(org.apache.skywalking.oap.server.core.remote.grpc.proto.IntKeyLongValuePair)(iterator.next());
-            super.getDetailGroup().put(new Integer(element.getKey()), new 
org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue(element.getKey(),
 element.getValue()));
+            super.${field.getter}().put(new Integer(element.getKey()), new 
org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue(element.getKey(),
 element.getValue()));
         }
     </#list>
 }
\ No newline at end of file
diff --git 
a/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl 
b/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
index f2960da..a65b610 100644
--- a/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
+++ b/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
@@ -15,11 +15,15 @@ public 
org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData.Builde
     <#list serializeFields.intFields as field>
         remoteBuilder.addDataIntegers(${field.getter}());
     </#list>
+    java.util.Iterator iterator;
+    
org.apache.skywalking.oap.server.core.remote.grpc.proto.DataIntLongPairList.Builder
 pairListBuilder;
     <#list serializeFields.intKeyLongValueHashMapFields as field>
-        java.util.Iterator iterator = 
super.getDetailGroup().values().iterator();
+        iterator = super.${field.getter}().values().iterator();
+        pairListBuilder = 
org.apache.skywalking.oap.server.core.remote.grpc.proto.DataIntLongPairList.newBuilder();
         while (iterator.hasNext()) {
-            
remoteBuilder.addDataIntLongPairList(((org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue)(iterator.next())).serialize());
+            
pairListBuilder.addValue(((org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue)(iterator.next())).serialize());
         }
+        remoteBuilder.addDataLists(pairListBuilder);
     </#list>
 
     return remoteBuilder;
diff --git 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
index 04e9564..040b693 100644
--- 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
+++ 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
@@ -19,5 +19,5 @@
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
 public enum MetricsValueType {
-    LONG, INT, DOUBLE
+    LONG, INT, DOUBLE, MULTI_INTS
 }
diff --git 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
index 6af7524..da1fa33 100644
--- 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
+++ 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
@@ -18,12 +18,19 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
 import org.apache.skywalking.oap.server.core.alarm.MetaInAlarm;
 import 
org.apache.skywalking.oap.server.core.analysis.metrics.DoubleValueHolder;
 import org.apache.skywalking.oap.server.core.analysis.metrics.IntValueHolder;
 import org.apache.skywalking.oap.server.core.analysis.metrics.LongValueHolder;
 import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.MultiIntValuesHolder;
 import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.joda.time.LocalDateTime;
 import org.joda.time.Minutes;
@@ -32,13 +39,6 @@ import org.joda.time.format.DateTimeFormatter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.ReentrantLock;
-
 /**
  * RunningRule represents each rule in running status. Based on the {@link 
AlarmRule} definition,
  *
@@ -86,7 +86,8 @@ public class RunningRule {
      * Receive metrics result from persistence, after it is saved into 
storage. In alarm, only minute dimensionality
      * metrics are expected to process.
      *
-     * @param metrics
+     * @param meta of input metrics
+     * @param metrics includes the values.
      */
     public void in(MetaInAlarm meta, Metrics metrics) {
         if (!meta.getMetricsName().equals(metricsName)) {
@@ -116,6 +117,9 @@ public class RunningRule {
             } else if (metrics instanceof DoubleValueHolder) {
                 valueType = MetricsValueType.DOUBLE;
                 threshold.setType(MetricsValueType.DOUBLE);
+            } else if (metrics instanceof MultiIntValuesHolder) {
+                valueType = MetricsValueType.MULTI_INTS;
+                threshold.setType(MetricsValueType.MULTI_INTS);
             } else {
                 return;
             }
@@ -126,8 +130,8 @@ public class RunningRule {
             Window window = windows.get(meta);
             if (window == null) {
                 window = new Window(period);
-                LocalDateTime timebucket = 
TIME_BUCKET_FORMATTER.parseLocalDateTime(metrics.getTimeBucket() + "");
-                window.moveTo(timebucket);
+                LocalDateTime timeBucket = 
TIME_BUCKET_FORMATTER.parseLocalDateTime(metrics.getTimeBucket() + "");
+                window.moveTo(timeBucket);
                 windows.put(meta, window);
             }
 
@@ -170,11 +174,9 @@ public class RunningRule {
         return alarmMessageList;
     }
 
-
-
     /**
-     * A metrics window, based on {@link AlarmRule#period}. This window slides 
with time, just keeps the recent
-     * N(period) buckets.
+     * A metrics window, based on AlarmRule#period. This window slides with 
time, just keeps the recent N(period)
+     * buckets.
      *
      * @author wusheng
      */
@@ -325,7 +327,7 @@ public class RunningRule {
                         break;
                     case DOUBLE:
                         double dvalue = 
((DoubleValueHolder)metrics).getValue();
-                        double dexpected = 
RunningRule.this.threshold.getDoubleThreadhold();
+                        double dexpected = 
RunningRule.this.threshold.getDoubleThreshold();
                         switch (op) {
                             case EQUAL:
                                 // NOTICE: double equal is not reliable in 
Java,
@@ -343,6 +345,41 @@ public class RunningRule {
                                 break;
                         }
                         break;
+                    case MULTI_INTS:
+                        int[] ivalueArray = 
((MultiIntValuesHolder)metrics).getValues();
+                        Integer[] iaexpected = 
RunningRule.this.threshold.getIntValuesThreshold();
+                        MULTI_VALUE_CHECK:
+                        for (int i = 0; i < ivalueArray.length; i++) {
+                            ivalue = ivalueArray[i];
+                            Integer iNullableExpected = 0;
+                            if (iaexpected.length > i) {
+                                iNullableExpected = iaexpected[i];
+                                if (iNullableExpected == null) {
+                                    continue;
+                                }
+                            }
+                            switch (op) {
+                                case LESS:
+                                    if (ivalue < iNullableExpected) {
+                                        matchCount++;
+                                        break MULTI_VALUE_CHECK;
+                                    }
+                                    break;
+                                case GREATER:
+                                    if (ivalue > iNullableExpected) {
+                                        matchCount++;
+                                        break MULTI_VALUE_CHECK;
+                                    }
+                                    break;
+                                case EQUAL:
+                                    if (ivalue == iNullableExpected) {
+                                        matchCount++;
+                                        break MULTI_VALUE_CHECK;
+                                    }
+                                    break;
+                            }
+                        }
+                        break;
                 }
             }
 
diff --git 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Threshold.java
 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Threshold.java
index aa67f3b..9906b89 100644
--- 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Threshold.java
+++ 
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Threshold.java
@@ -26,12 +26,14 @@ import org.slf4j.LoggerFactory;
  */
 public class Threshold {
     private static final Logger logger = 
LoggerFactory.getLogger(Threshold.class);
+    private static final String NONE_THRESHOLD = "-";
 
     private String alarmRuleName;
     private final String threshold;
     private int intThreshold;
-    private double doubleThreadhold;
+    private double doubleThreshold;
     private long longThreshold;
+    private Integer[] intValuesThreshold;
 
     public Threshold(String alarmRuleName, String threshold) {
         this.alarmRuleName = alarmRuleName;
@@ -42,14 +44,18 @@ public class Threshold {
         return intThreshold;
     }
 
-    public double getDoubleThreadhold() {
-        return doubleThreadhold;
+    public double getDoubleThreshold() {
+        return doubleThreshold;
     }
 
     public long getLongThreshold() {
         return longThreshold;
     }
 
+    public Integer[] getIntValuesThreshold() {
+        return intValuesThreshold;
+    }
+
     public void setType(MetricsValueType type) {
         try {
             switch (type) {
@@ -60,8 +66,19 @@ public class Threshold {
                     longThreshold = Long.parseLong(threshold);
                     break;
                 case DOUBLE:
-                    doubleThreadhold = Double.parseDouble(threshold);
+                    doubleThreshold = Double.parseDouble(threshold);
                     break;
+                case MULTI_INTS:
+                    String[] strings = threshold.split(",");
+                    intValuesThreshold = new Integer[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        String thresholdItem = strings[i].trim();
+                        if (NONE_THRESHOLD.equals(thresholdItem)) {
+                            intValuesThreshold[i] = null;
+                        } else {
+                            intValuesThreshold[i] = 
Integer.parseInt(thresholdItem);
+                        }
+                    }
             }
         } catch (NumberFormatException e) {
             logger.warn("Alarm rule {} threshold doesn't match the metrics 
type, expected type: {}", alarmRuleName, type);
diff --git 
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
 
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
index a07a593..016a855 100644
--- 
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
+++ 
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRuleTest.java
@@ -103,6 +103,42 @@ public class RunningRuleTest {
     }
 
     @Test
+    public void testMultipleValuesAlarm() {
+        AlarmRule alarmRule = new AlarmRule();
+        alarmRule.setAlarmRuleName("endpoint_multiple_values_rule");
+        alarmRule.setMetricsName("endpoint_percent");
+        alarmRule.setOp(">");
+        alarmRule.setThreshold("50,60,70,-, 100");
+        alarmRule.setCount(3);
+        alarmRule.setPeriod(15);
+        alarmRule.setMessage("response percentile of endpoint {name} is lower 
than expected values");
+
+        RunningRule runningRule = new RunningRule(alarmRule);
+        LocalDateTime startTime = 
TIME_BUCKET_FORMATTER.parseLocalDateTime("201808301440");
+
+        long timeInPeriod1 = 201808301434L;
+        long timeInPeriod2 = 201808301436L;
+        long timeInPeriod3 = 201808301438L;
+
+        runningRule.in(getMetaInAlarm(123), 
getMultipleValueMetrics(timeInPeriod1, 70, 60, 40, 40, 40));
+        runningRule.in(getMetaInAlarm(123), 
getMultipleValueMetrics(timeInPeriod2, 60, 60, 40, 40, 40));
+        runningRule.in(getMetaInAlarm(123), 
getMultipleValueMetrics(timeInPeriod3, 74, 60, 40, 40, 40));
+
+        // check at 201808301440
+        List<AlarmMessage> alarmMessages = runningRule.check();
+        Assert.assertEquals(0, alarmMessages.size());
+        
runningRule.moveTo(TIME_BUCKET_FORMATTER.parseLocalDateTime("201808301441"));
+        // check at 201808301441
+        alarmMessages = runningRule.check();
+        Assert.assertEquals(0, alarmMessages.size());
+        
runningRule.moveTo(TIME_BUCKET_FORMATTER.parseLocalDateTime("201808301442"));
+        // check at 201808301442
+        alarmMessages = runningRule.check();
+        Assert.assertEquals(1, alarmMessages.size());
+        Assert.assertEquals("response percentile of endpoint Service_123 is 
lower than expected values", alarmMessages.get(0).getAlarmMessage());
+    }
+
+    @Test
     public void testNoAlarm() {
         AlarmRule alarmRule = new AlarmRule();
         alarmRule.setAlarmRuleName("endpoint_percent_rule");
@@ -258,6 +294,14 @@ public class RunningRuleTest {
         return mockMetrics;
     }
 
+    private Metrics getMultipleValueMetrics(long timeBucket, int... values) {
+        MockMultipleValueMetrics mockMultipleValueMetrics = new 
MockMultipleValueMetrics();
+        mockMultipleValueMetrics.setValues(values);
+        mockMultipleValueMetrics.setTimeBucket(timeBucket);
+        return mockMultipleValueMetrics;
+
+    }
+
     private class MockMetrics extends Metrics implements IntValueHolder {
         private int value;
 
@@ -305,4 +349,52 @@ public class RunningRuleTest {
             return 0;
         }
     }
+
+    private class MockMultipleValueMetrics extends Metrics implements 
MultiIntValuesHolder {
+        private int[] values;
+
+        public void setValues(int[] values) {
+            this.values = values;
+        }
+
+        @Override public String id() {
+            return null;
+        }
+
+        @Override public void combine(Metrics metrics) {
+
+        }
+
+        @Override public void calculate() {
+
+        }
+
+        @Override public Metrics toHour() {
+            return null;
+        }
+
+        @Override public Metrics toDay() {
+            return null;
+        }
+
+        @Override public Metrics toMonth() {
+            return null;
+        }
+
+        @Override public int[] getValues() {
+            return values;
+        }
+
+        @Override public int remoteHashCode() {
+            return 0;
+        }
+
+        @Override public void deserialize(RemoteData remoteData) {
+
+        }
+
+        @Override public RemoteData.Builder serialize() {
+            return null;
+        }
+    }
 }
diff --git 
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/ThresholdTest.java
 
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/ThresholdTest.java
index 0363b48..913f001 100644
--- 
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/ThresholdTest.java
+++ 
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/ThresholdTest.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.server.core.alarm.provider;
 
 import org.junit.Test;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -31,7 +32,7 @@ public class ThresholdTest {
     public void setType() {
         Threshold threshold = new Threshold("my-rule", "75");
         threshold.setType(MetricsValueType.DOUBLE);
-        assertEquals(0, Double.compare(75, threshold.getDoubleThreadhold()));
+        assertEquals(0, Double.compare(75, threshold.getDoubleThreshold()));
 
         threshold.setType(MetricsValueType.INT);
         assertEquals(75, threshold.getIntThreshold());
@@ -41,6 +42,14 @@ public class ThresholdTest {
     }
 
     @Test
+    public void setTypeMultipleValues() {
+        Threshold threshold = new Threshold("my-rule", "75,80, 90, -");
+        threshold.setType(MetricsValueType.MULTI_INTS);
+        assertArrayEquals(new Object[] {75, 80, 90, null}, 
threshold.getIntValuesThreshold());
+
+    }
+
+    @Test
     public void setTypeWithWrong() {
         Threshold threshold = new Threshold("my-rule", "wrong");
         threshold.setType(MetricsValueType.INT);
diff --git 
a/oap-server/server-bootstrap/src/main/resources/official_analysis.oal 
b/oap-server/server-bootstrap/src/main/resources/official_analysis.oal
index da9b92c..c7cd75d 100755
--- a/oap-server/server-bootstrap/src/main/resources/official_analysis.oal
+++ b/oap-server/server-bootstrap/src/main/resources/official_analysis.oal
@@ -17,22 +17,14 @@
  */
 
 // All scope metrics
-all_p99 = from(All.latency).p99(10);
-all_p95 = from(All.latency).p95(10);
-all_p90 = from(All.latency).p90(10);
-all_p75 = from(All.latency).p75(10);
-all_p50 = from(All.latency).p50(10);
+all_percentile = from(All.latency).percentile(10);  // Multiple values 
including p50, p75, p90, p95, p99
 all_heatmap = from(All.latency).thermodynamic(100, 20);
 
 // Service scope metrics
 service_resp_time = from(Service.latency).longAvg();
 service_sla = from(Service.*).percent(status == true);
 service_cpm = from(Service.*).cpm();
-service_p99 = from(Service.latency).p99(10);
-service_p95 = from(Service.latency).p95(10);
-service_p90 = from(Service.latency).p90(10);
-service_p75 = from(Service.latency).p75(10);
-service_p50 = from(Service.latency).p50(10);
+service_percentile = from(Service.latency).percentile(10); // Multiple values 
including p50, p75, p90, p95, p99
 service_apdex = from(Service.latency).apdex(name, status);
 
 // Service relation scope metrics for topology
@@ -42,16 +34,9 @@ service_relation_client_call_sla = 
from(ServiceRelation.*).filter(detectPoint ==
 service_relation_server_call_sla = from(ServiceRelation.*).filter(detectPoint 
== DetectPoint.SERVER).percent(status == true);
 service_relation_client_resp_time = 
from(ServiceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).longAvg();
 service_relation_server_resp_time = 
from(ServiceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).longAvg();
-service_relation_client_p99 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.CLIENT).p99(10);
-service_relation_server_p99 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.SERVER).p99(10);
-service_relation_client_p95 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.CLIENT).p95(10);
-service_relation_server_p95 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.SERVER).p95(10);
-service_relation_client_p90 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.CLIENT).p90(10);
-service_relation_server_p90 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.SERVER).p90(10);
-service_relation_client_p75 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.CLIENT).p75(10);
-service_relation_server_p75 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.SERVER).p75(10);
-service_relation_client_p50 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.CLIENT).p50(10);
-service_relation_server_p50 = from(ServiceRelation.latency).filter(detectPoint 
== DetectPoint.SERVER).p50(10);
+service_relation_client_percentile = 
from(ServiceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).percentile(10); // Multiple values including p50, p75, p90, 
p95, p99
+service_relation_server_percentile = 
from(ServiceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).percentile(10); // Multiple values including p50, p75, p90, 
p95, p99
+
 
 // Service Instance relation scope metrics for topology
 service_instance_relation_client_cpm = 
from(ServiceInstanceRelation.*).filter(detectPoint == DetectPoint.CLIENT).cpm();
@@ -60,16 +45,8 @@ service_instance_relation_client_call_sla = 
from(ServiceInstanceRelation.*).filt
 service_instance_relation_server_call_sla = 
from(ServiceInstanceRelation.*).filter(detectPoint == 
DetectPoint.SERVER).percent(status == true);
 service_instance_relation_client_resp_time = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).longAvg();
 service_instance_relation_server_resp_time = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).longAvg();
-service_instance_relation_client_p99 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).p99(10);
-service_instance_relation_server_p99 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).p99(10);
-service_instance_relation_client_p95 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).p95(10);
-service_instance_relation_server_p95 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).p95(10);
-service_instance_relation_client_p90 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).p90(10);
-service_instance_relation_server_p90 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).p90(10);
-service_instance_relation_client_p75 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).p75(10);
-service_instance_relation_server_p75 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).p75(10);
-service_instance_relation_client_p50 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).p50(10);
-service_instance_relation_server_p50 = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).p50(10);
+service_instance_relation_client_percentile = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.CLIENT).percentile(10); // Multiple values including p50, p75, p90, 
p95, p99
+service_instance_relation_server_percentile = 
from(ServiceInstanceRelation.latency).filter(detectPoint == 
DetectPoint.SERVER).percentile(10); // Multiple values including p50, p75, p90, 
p95, p99
 
 // Service Instance Scope metrics
 service_instance_sla = from(ServiceInstance.*).percent(status == true);
@@ -80,11 +57,7 @@ service_instance_cpm = from(ServiceInstance.*).cpm();
 endpoint_cpm = from(Endpoint.*).cpm();
 endpoint_avg = from(Endpoint.latency).longAvg();
 endpoint_sla = from(Endpoint.*).percent(status == true);
-endpoint_p99 = from(Endpoint.latency).p99(10);
-endpoint_p95 = from(Endpoint.latency).p95(10);
-endpoint_p90 = from(Endpoint.latency).p90(10);
-endpoint_p75 = from(Endpoint.latency).p75(10);
-endpoint_p50 = from(Endpoint.latency).p50(10);
+endpoint_percentile = from(Endpoint.latency).percentile(10); // Multiple 
values including p50, p75, p90, p95, p99
 
 // Endpoint relation scope metrics
 endpoint_relation_cpm = from(EndpointRelation.*).filter(detectPoint == 
DetectPoint.SERVER).cpm();
@@ -104,11 +77,7 @@ instance_jvm_old_gc_count = 
from(ServiceInstanceJVMGC.count).filter(phrase == GC
 database_access_resp_time = from(DatabaseAccess.latency).longAvg();
 database_access_sla = from(DatabaseAccess.*).percent(status == true);
 database_access_cpm = from(DatabaseAccess.*).cpm();
-database_access_p99 = from(DatabaseAccess.latency).p99(10);
-database_access_p95 = from(DatabaseAccess.latency).p95(10);
-database_access_p90 = from(DatabaseAccess.latency).p90(10);
-database_access_p75 = from(DatabaseAccess.latency).p75(10);
-database_access_p50 = from(DatabaseAccess.latency).p50(10);
+database_access_percentile = from(DatabaseAccess.latency).percentile(10);
 
 // CLR instance metrics
 instance_clr_cpu = from(ServiceInstanceCLRCPU.usePercent).doubleAvg();
diff --git 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/MultiIntValuesHolder.java
similarity index 79%
copy from 
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
copy to 
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/MultiIntValuesHolder.java
index 04e9564..f8fefd5 100644
--- 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/MultiIntValuesHolder.java
@@ -16,8 +16,13 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider;
+package org.apache.skywalking.oap.server.core.analysis.metrics;
 
-public enum MetricsValueType {
-    LONG, INT, DOUBLE
+/**
+ * MultiIntValuesHolder always holds a set of int(s).
+ *
+ * @author wusheng
+ */
+public interface MultiIntValuesHolder {
+    int[] getValues();
 }
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
new file mode 100644
index 0000000..5f86cab
--- /dev/null
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
@@ -0,0 +1,120 @@
+/*
+ * 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.server.core.analysis.metrics;
+
+import java.util.Comparator;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Arg;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Entrance;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.annotation.MetricsFunction;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.annotation.SourceFrom;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+
+/**
+ * Percentile is a better implementation than {@link PxxMetrics}. It is 
introduced since 7.0.0, it could calculate the
+ * multiple P50/75/90/95/99 values once for all.
+ *
+ * @author wusheng
+ */
+@MetricsFunction(functionName = "percentile")
+public abstract class PercentileMetrics extends GroupMetrics implements 
MultiIntValuesHolder {
+    protected static final String DATASET = "dataset";
+    protected static final String VALUE = "value";
+    protected static final String PRECISION = "precision";
+
+    private static final int[] RANKS = {50, 75, 90, 95, 99};
+
+    @Getter @Setter @Column(columnName = VALUE, isValue = true) private 
IntKeyLongValueHashMap percentileValues;
+    @Getter @Setter @Column(columnName = PRECISION) private int precision;
+    @Getter @Setter @Column(columnName = DATASET) private 
IntKeyLongValueHashMap dataset;
+
+    private boolean isCalculated;
+
+    public PercentileMetrics() {
+        percentileValues = new IntKeyLongValueHashMap(RANKS.length);
+        dataset = new IntKeyLongValueHashMap(30);
+    }
+
+    @Entrance
+    public final void combine(@SourceFrom int value, @Arg int precision) {
+        this.isCalculated = false;
+        this.precision = precision;
+
+        int index = value / precision;
+        IntKeyLongValue element = dataset.get(index);
+        if (element == null) {
+            element = new IntKeyLongValue(index, 1);
+            dataset.put(element.getKey(), element);
+        } else {
+            element.addValue(1);
+        }
+    }
+
+    @Override
+    public void combine(Metrics metrics) {
+        this.isCalculated = false;
+
+        PercentileMetrics percentileMetrics = (PercentileMetrics)metrics;
+        combine(percentileMetrics.getDataset(), this.dataset);
+    }
+
+    @Override
+    public final void calculate() {
+
+        if (!isCalculated) {
+            int total = dataset.values().stream().mapToInt(element -> 
(int)element.getValue()).sum();
+
+            int index = 0;
+            int[] roofs = new int[RANKS.length];
+            for (int i = 0; i < RANKS.length; i++) {
+                roofs[i] = Math.round(total * RANKS[i] * 1.0f / 100);
+            }
+
+            int count = 0;
+            IntKeyLongValue[] sortedData = 
dataset.values().stream().sorted(new Comparator<IntKeyLongValue>() {
+                @Override public int compare(IntKeyLongValue o1, 
IntKeyLongValue o2) {
+                    return o1.getKey() - o2.getKey();
+                }
+            }).toArray(IntKeyLongValue[]::new);
+            for (IntKeyLongValue element : sortedData) {
+                count += element.getValue();
+                for (int i = index; i < roofs.length; i++) {
+                    int roof = roofs[i];
+
+                    if (count >= roof) {
+                        percentileValues.put(index, new IntKeyLongValue(index, 
element.getKey() * precision));
+                        index++;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    public int[] getValues() {
+        int[] values = new int[percentileValues.size()];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = (int)percentileValues.get(i).getValue();
+        }
+        return values;
+    }
+}
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
index a438793..a53930f 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
@@ -60,7 +60,7 @@ public class MetricQueryService implements Service {
         return metricQueryDAO;
     }
 
-    public IntValues getValues(final String indName, final List<String> ids, 
final Downsampling downsampling,
+    public IntValues getValues(final String metricsName, final List<String> 
ids, final Downsampling downsampling,
         final long startTB,
         final long endTB) throws IOException {
         if (CollectionUtils.isEmpty(ids)) {
@@ -69,7 +69,7 @@ public class MetricQueryService implements Service {
              * we return an empty list, and a debug level log,
              * rather than an exception, which always being considered as a 
serious error from new users.
              */
-            logger.debug("query metrics[{}] w/o IDs", indName);
+            logger.debug("query metrics[{}] w/o IDs", metricsName);
             return new IntValues();
         }
 
@@ -79,7 +79,7 @@ public class MetricQueryService implements Service {
         where.getKeyValues().add(intKeyValues);
         ids.forEach(intKeyValues.getValues()::add);
 
-        return getMetricQueryDAO().getValues(indName, downsampling, startTB, 
endTB, where, ValueColumnIds.INSTANCE.getValueCName(indName), 
ValueColumnIds.INSTANCE.getValueFunction(indName));
+        return getMetricQueryDAO().getValues(metricsName, downsampling, 
startTB, endTB, where, ValueColumnIds.INSTANCE.getValueCName(metricsName), 
ValueColumnIds.INSTANCE.getValueFunction(metricsName));
     }
 
     public IntValues getLinearIntValues(final String indName, final String id, 
final Downsampling downsampling,
@@ -96,6 +96,27 @@ public class MetricQueryService implements Service {
         return getMetricQueryDAO().getLinearIntValues(indName, downsampling, 
ids, ValueColumnIds.INSTANCE.getValueCName(indName));
     }
 
+    public List<IntValues> getMultipleLinearIntValues(final String indName, 
final String id, final int numOfLinear,
+        final Downsampling downsampling,
+        final long startTB,
+        final long endTB) throws IOException, ParseException {
+        List<DurationPoint> durationPoints = 
DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
+        List<String> ids = new ArrayList<>();
+        if (StringUtil.isEmpty(id)) {
+            durationPoints.forEach(durationPoint -> 
ids.add(String.valueOf(durationPoint.getPoint())));
+        } else {
+            durationPoints.forEach(durationPoint -> 
ids.add(durationPoint.getPoint() + Const.ID_SPLIT + id));
+        }
+
+        IntValues[] multipleLinearIntValues = 
getMetricQueryDAO().getMultipleLinearIntValues(indName, downsampling, ids, 
numOfLinear, ValueColumnIds.INSTANCE.getValueCName(indName));
+
+        ArrayList<IntValues> response = new ArrayList<IntValues>(numOfLinear);
+        for (IntValues value : multipleLinearIntValues) {
+            response.add(value);
+        }
+        return response;
+    }
+
     public Thermodynamic getThermodynamic(final String indName, final String 
id, final Downsampling downsampling,
         final long startTB,
         final long endTB) throws IOException, ParseException {
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/entity/IntValues.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/entity/IntValues.java
index 720fbf4..3525c60 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/entity/IntValues.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/entity/IntValues.java
@@ -19,14 +19,13 @@
 package org.apache.skywalking.oap.server.core.query.entity;
 
 import java.util.LinkedList;
-import java.util.List;
 
 /**
- * @author peng-yongsheng
+ * @author peng-yongsheng, wusheng
  */
 public class IntValues {
 
-    private List<KVInt> values = new LinkedList<>();
+    private LinkedList<KVInt> values = new LinkedList<>();
 
     public void addKVInt(KVInt e) {
         values.add(e);
@@ -40,4 +39,8 @@ public class IntValues {
         }
         return defaultValue;
     }
+
+    public KVInt getLast() {
+        return values.getLast();
+    }
 }
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnIds.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnIds.java
index b25622c..ef00307 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnIds.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnIds.java
@@ -33,12 +33,20 @@ public enum ValueColumnIds {
         mapping.putIfAbsent(indName, new ValueColumn(valueCName, function));
     }
 
-    public String getValueCName(String indName) {
-        return mapping.get(indName).valueCName;
+    public String getValueCName(String metricsName) {
+        return findColumn(metricsName).valueCName;
     }
 
-    public Function getValueFunction(String indName) {
-        return mapping.get(indName).function;
+    public Function getValueFunction(String metricsName) {
+        return findColumn(metricsName).function;
+    }
+
+    private ValueColumn findColumn(String metricsName) {
+        ValueColumn column = mapping.get(metricsName);
+        if (column == null) {
+            throw new RuntimeException("Metrics:" + metricsName + " doesn't 
have value column definition");
+        }
+        return column;
     }
 
     class ValueColumn {
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
index ff92d41..c10aafa 100644
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
@@ -34,5 +34,7 @@ public interface IMetricsQueryDAO extends DAO {
 
     IntValues getLinearIntValues(String indName, Downsampling downsampling, 
List<String> ids, String valueCName) throws IOException;
 
+    IntValues[] getMultipleLinearIntValues(String indName, Downsampling 
downsampling, List<String> ids, int numOfLinear, String valueCName) throws 
IOException;
+
     Thermodynamic getThermodynamic(String indName, Downsampling downsampling, 
List<String> ids, String valueCName) throws IOException;
 }
diff --git a/oap-server/server-core/src/main/proto/RemoteService.proto 
b/oap-server/server-core/src/main/proto/RemoteService.proto
index 73ed7a6..48b9ca3 100644
--- a/oap-server/server-core/src/main/proto/RemoteService.proto
+++ b/oap-server/server-core/src/main/proto/RemoteService.proto
@@ -36,7 +36,11 @@ message RemoteData {
     repeated int64 dataLongs = 2;
     repeated double dataDoubles = 3;
     repeated int32 dataIntegers = 4;
-    repeated IntKeyLongValuePair dataIntLongPairList = 5;
+    repeated DataIntLongPairList dataLists = 5;
+}
+
+message DataIntLongPairList {
+    repeated IntKeyLongValuePair value = 1;
 }
 
 message IntKeyLongValuePair {
diff --git 
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetricsTest.java
 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetricsTest.java
new file mode 100644
index 0000000..917842a
--- /dev/null
+++ 
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetricsTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.server.core.analysis.metrics;
+
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author wusheng
+ */
+public class PercentileMetricsTest {
+    private int precision = 10;//ms
+
+    @Test
+    public void percentileTest() {
+        PercentileMetricsTest.PercentileMetricsMocker metricsMocker = new 
PercentileMetricsTest.PercentileMetricsMocker();
+
+        metricsMocker.combine(110, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(95, precision);
+        metricsMocker.combine(99, precision);
+        metricsMocker.combine(50, precision);
+        metricsMocker.combine(50, precision);
+        metricsMocker.combine(50, precision);
+        metricsMocker.combine(50, precision);
+        metricsMocker.combine(50, precision);
+        metricsMocker.combine(75, precision);
+        metricsMocker.combine(75, precision);
+
+        metricsMocker.calculate();
+
+        Assert.assertArrayEquals(new int[] {70, 90, 90, 90, 110}, 
metricsMocker.getValues());
+    }
+
+    @Test
+    public void percentileTest2() {
+        PercentileMetricsTest.PercentileMetricsMocker metricsMocker = new 
PercentileMetricsTest.PercentileMetricsMocker();
+
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(90, precision);
+
+        metricsMocker.calculate();
+
+        Assert.assertArrayEquals(new int[] {90, 90, 90, 90, 90}, 
metricsMocker.getValues());
+    }
+
+    @Test
+    public void percentileTest3() {
+        PercentileMetricsTest.PercentileMetricsMocker metricsMocker = new 
PercentileMetricsTest.PercentileMetricsMocker();
+
+        metricsMocker.combine(90, precision);
+        metricsMocker.combine(110, precision);
+
+        metricsMocker.calculate();
+
+        Assert.assertArrayEquals(new int[] {90, 110, 110, 110, 110}, 
metricsMocker.getValues());
+    }
+
+    @Test
+    public void percentileTest4() {
+        PercentileMetricsTest.PercentileMetricsMocker metricsMocker = new 
PercentileMetricsTest.PercentileMetricsMocker();
+
+        metricsMocker.combine(0, precision);
+        metricsMocker.combine(0, precision);
+
+        metricsMocker.calculate();
+
+        Assert.assertArrayEquals(new int[] {0, 0, 0, 0, 0}, 
metricsMocker.getValues());
+    }
+
+    public class PercentileMetricsMocker extends PercentileMetrics {
+
+        @Override public String id() {
+            return null;
+        }
+
+        @Override public Metrics toHour() {
+            return null;
+        }
+
+        @Override public Metrics toDay() {
+            return null;
+        }
+
+        @Override public Metrics toMonth() {
+            return null;
+        }
+
+        @Override public int remoteHashCode() {
+            return 0;
+        }
+
+        @Override public void deserialize(RemoteData remoteData) {
+
+        }
+
+        @Override public RemoteData.Builder serialize() {
+            return null;
+        }
+    }
+}
diff --git 
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
 
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
index 690420a..b4f948f 100644
--- 
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
+++ 
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
@@ -21,6 +21,7 @@ package org.apache.skywalking.oap.query.graphql.resolver;
 import com.coxautodev.graphql.tools.GraphQLQueryResolver;
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.List;
 import org.apache.skywalking.oap.query.graphql.type.*;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.query.*;
@@ -60,6 +61,13 @@ public class MetricQuery implements GraphQLQueryResolver {
         return getMetricQueryService().getLinearIntValues(metrics.getName(), 
metrics.getId(), StepToDownsampling.transform(duration.getStep()), 
startTimeBucket, endTimeBucket);
     }
 
+    public List<IntValues> getMultipleLinearIntValues(final MetricCondition 
metrics, final int numOfLinear, final Duration duration) throws IOException, 
ParseException {
+        long startTimeBucket = 
DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getStart());
+        long endTimeBucket = 
DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getEnd());
+
+        return 
getMetricQueryService().getMultipleLinearIntValues(metrics.getName(), 
metrics.getId(), numOfLinear, StepToDownsampling.transform(duration.getStep()), 
startTimeBucket, endTimeBucket);
+    }
+
     public Thermodynamic getThermodynamic(final MetricCondition metrics, final 
Duration duration) throws IOException, ParseException {
         long startTimeBucket = 
DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getStart());
         long endTimeBucket = 
DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getEnd());
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 dde9a0d..249adde 160000
--- 
a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
+++ 
b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
@@ -1 +1 @@
-Subproject commit dde9a0dad56617ccbf4226f5f71e667fd9620222
+Subproject commit 249addeaaf524c0dd990444e5f4bcaf355ce8e01
diff --git 
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
 
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
index fde2d6d..7325fd4 100644
--- 
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
+++ 
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
@@ -19,11 +19,20 @@
 package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query;
 
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import org.apache.skywalking.oap.server.core.analysis.Downsampling;
-import org.apache.skywalking.oap.server.core.analysis.metrics.*;
-import org.apache.skywalking.oap.server.core.query.entity.*;
-import org.apache.skywalking.oap.server.core.query.sql.*;
+import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.ThermodynamicMetrics;
+import org.apache.skywalking.oap.server.core.query.entity.IntValues;
+import org.apache.skywalking.oap.server.core.query.entity.KVInt;
+import org.apache.skywalking.oap.server.core.query.entity.Thermodynamic;
+import org.apache.skywalking.oap.server.core.query.sql.Function;
+import org.apache.skywalking.oap.server.core.query.sql.Where;
 import org.apache.skywalking.oap.server.core.storage.model.ModelName;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import 
org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
@@ -31,7 +40,8 @@ import 
org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.EsDAO;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.bucket.terms.*;
+import org.elasticsearch.search.aggregations.bucket.terms.Terms;
+import 
org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
 import org.elasticsearch.search.aggregations.metrics.avg.Avg;
 import org.elasticsearch.search.aggregations.metrics.sum.Sum;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
@@ -45,7 +55,9 @@ public class MetricsQueryEsDAO extends EsDAO implements 
IMetricsQueryDAO {
         super(client);
     }
 
-    @Override public IntValues getValues(String indName, Downsampling 
downsampling, long startTB, long endTB, Where where, String valueCName,
+    @Override
+    public IntValues getValues(String indName, Downsampling downsampling, long 
startTB, long endTB, Where where,
+        String valueCName,
         Function function) throws IOException {
         String indexName = ModelName.build(downsampling, indName);
 
@@ -100,7 +112,8 @@ public class MetricsQueryEsDAO extends EsDAO implements 
IMetricsQueryDAO {
         }
     }
 
-    @Override public IntValues getLinearIntValues(String indName, Downsampling 
downsampling, List<String> ids, String valueCName) throws IOException {
+    @Override public IntValues getLinearIntValues(String indName, Downsampling 
downsampling, List<String> ids,
+        String valueCName) throws IOException {
         String indexName = ModelName.build(downsampling, indName);
 
         SearchResponse response = getClient().ids(indexName, ids.toArray(new 
String[0]));
@@ -121,7 +134,43 @@ public class MetricsQueryEsDAO extends EsDAO implements 
IMetricsQueryDAO {
         return intValues;
     }
 
-    @Override public Thermodynamic getThermodynamic(String indName, 
Downsampling downsampling, List<String> ids, String valueCName) throws 
IOException {
+    @Override public IntValues[] getMultipleLinearIntValues(String indName, 
Downsampling downsampling,
+        List<String> ids, int numOfLinear, String valueCName) throws 
IOException {
+        String indexName = ModelName.build(downsampling, indName);
+
+        SearchResponse response = getClient().ids(indexName, ids.toArray(new 
String[0]));
+        Map<String, Map<String, Object>> idMap = toMap(response);
+
+        IntValues[] intValuesArray = new IntValues[numOfLinear];
+        for (int i = 0; i < intValuesArray.length; i++) {
+            intValuesArray[i] = new IntValues();
+        }
+
+        for (String id : ids) {
+            for (int i = 0; i < intValuesArray.length; i++) {
+                KVInt kvInt = new KVInt();
+                kvInt.setId(id);
+                kvInt.setValue(0);
+                intValuesArray[i].addKVInt(kvInt);
+            }
+
+            if (idMap.containsKey(id)) {
+                Map<String, Object> source = idMap.get(id);
+                IntKeyLongValueHashMap multipleValues = new 
IntKeyLongValueHashMap(5);
+                
multipleValues.toObject((String)source.getOrDefault(valueCName, ""));
+
+                for (int i = 0; i < intValuesArray.length; i++) {
+                    
intValuesArray[i].getLast().setValue(multipleValues.get(i).getValue());
+                }
+            }
+
+        }
+
+        return intValuesArray;
+    }
+
+    @Override public Thermodynamic getThermodynamic(String indName, 
Downsampling downsampling, List<String> ids,
+        String valueCName) throws IOException {
         String indexName = ModelName.build(downsampling, indName);
 
         Thermodynamic thermodynamic = new Thermodynamic();
diff --git 
a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
 
b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
index 5937252..0e24925 100644
--- 
a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
+++ 
b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
@@ -19,12 +19,24 @@
 package org.apache.skywalking.oap.server.storage.plugin.jdbc.h2.dao;
 
 import java.io.IOException;
-import java.sql.*;
-import java.util.*;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import org.apache.skywalking.oap.server.core.analysis.Downsampling;
-import org.apache.skywalking.oap.server.core.analysis.metrics.*;
-import org.apache.skywalking.oap.server.core.query.entity.*;
-import org.apache.skywalking.oap.server.core.query.sql.*;
+import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
+import 
org.apache.skywalking.oap.server.core.analysis.metrics.ThermodynamicMetrics;
+import org.apache.skywalking.oap.server.core.query.entity.IntValues;
+import org.apache.skywalking.oap.server.core.query.entity.KVInt;
+import org.apache.skywalking.oap.server.core.query.entity.Thermodynamic;
+import org.apache.skywalking.oap.server.core.query.sql.Function;
+import org.apache.skywalking.oap.server.core.query.sql.KeyValues;
+import org.apache.skywalking.oap.server.core.query.sql.Where;
 import org.apache.skywalking.oap.server.core.storage.model.ModelName;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import 
org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCHikariCPClient;
@@ -41,7 +53,8 @@ public class H2MetricsQueryDAO extends H2SQLExecutor 
implements IMetricsQueryDAO
     }
 
     @Override
-    public IntValues getValues(String indName, Downsampling downsampling, long 
startTB, long endTB, Where where, String valueCName,
+    public IntValues getValues(String indName, Downsampling downsampling, long 
startTB, long endTB, Where where,
+        String valueCName,
         Function function) throws IOException {
         String tableName = ModelName.build(downsampling, indName);
 
@@ -100,7 +113,8 @@ public class H2MetricsQueryDAO extends H2SQLExecutor 
implements IMetricsQueryDAO
         return orderWithDefault0(intValues, ids);
     }
 
-    @Override public IntValues getLinearIntValues(String indName, Downsampling 
downsampling, List<String> ids, String valueCName) throws IOException {
+    @Override public IntValues getLinearIntValues(String indName, Downsampling 
downsampling, List<String> ids,
+        String valueCName) throws IOException {
         String tableName = ModelName.build(downsampling, indName);
 
         StringBuilder idValues = new StringBuilder();
@@ -129,6 +143,48 @@ public class H2MetricsQueryDAO extends H2SQLExecutor 
implements IMetricsQueryDAO
         return orderWithDefault0(intValues, ids);
     }
 
+    @Override public IntValues[] getMultipleLinearIntValues(String indName, 
Downsampling downsampling,
+        List<String> ids,
+        int numOfLinear,
+        String valueCName) throws IOException {
+        String tableName = ModelName.build(downsampling, indName);
+
+        StringBuilder idValues = new StringBuilder();
+        for (int valueIdx = 0; valueIdx < ids.size(); valueIdx++) {
+            if (valueIdx != 0) {
+                idValues.append(",");
+            }
+            idValues.append("'").append(ids.get(valueIdx)).append("'");
+        }
+
+        IntValues[] intValuesArray = new IntValues[numOfLinear];
+        for (int i = 0; i < intValuesArray.length; i++) {
+            intValuesArray[i] = new IntValues();
+        }
+
+        try (Connection connection = h2Client.getConnection()) {
+            try (ResultSet resultSet = h2Client.executeQuery(connection, 
"select id, " + valueCName + " from " + tableName + " where id in (" + 
idValues.toString() + ")")) {
+                while (resultSet.next()) {
+                    String id = resultSet.getString("id");
+
+                    IntKeyLongValueHashMap multipleValues = new 
IntKeyLongValueHashMap(5);
+                    multipleValues.toObject(resultSet.getString(valueCName));
+
+                    for (int i = 0; i < intValuesArray.length; i++) {
+                        KVInt kv = new KVInt();
+                        kv.setId(id);
+                        kv.setValue(multipleValues.get(i).getValue());
+                        intValuesArray[i].addKVInt(kv);
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            throw new IOException(e);
+        }
+
+        return orderWithDefault0(intValuesArray, ids);
+    }
+
     /**
      * Make sure the order is same as the expected order, and keep default 
value as 0.
      *
@@ -149,7 +205,22 @@ public class H2MetricsQueryDAO extends H2SQLExecutor 
implements IMetricsQueryDAO
         return intValues;
     }
 
-    @Override public Thermodynamic getThermodynamic(String indName, 
Downsampling downsampling, List<String> ids, String valueCName) throws 
IOException {
+    /**
+     * Make sure the order is same as the expected order, and keep default 
value as 0.
+     *
+     * @param origin
+     * @param expectedOrder
+     * @return
+     */
+    private IntValues[] orderWithDefault0(IntValues[] origin, List<String> 
expectedOrder) {
+        for (int i = 0; i < origin.length; i++) {
+            origin[i] = orderWithDefault0(origin[i], expectedOrder);
+        }
+        return origin;
+    }
+
+    @Override public Thermodynamic getThermodynamic(String indName, 
Downsampling downsampling, List<String> ids,
+        String valueCName) throws IOException {
         String tableName = ModelName.build(downsampling, indName);
 
         StringBuilder idValues = new StringBuilder();
diff --git 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
index 7b2509b..caae4fb 100644
--- 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
+++ 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
@@ -22,6 +22,7 @@ import com.google.common.io.Resources;
 import org.apache.skywalking.e2e.metrics.Metrics;
 import org.apache.skywalking.e2e.metrics.MetricsData;
 import org.apache.skywalking.e2e.metrics.MetricsQuery;
+import org.apache.skywalking.e2e.metrics.MultiMetricsData;
 import org.apache.skywalking.e2e.service.Service;
 import org.apache.skywalking.e2e.service.ServicesData;
 import org.apache.skywalking.e2e.service.ServicesQuery;
@@ -225,4 +226,29 @@ public class SimpleQueryClient {
         return 
Objects.requireNonNull(responseEntity.getBody()).getData().getMetrics();
     }
 
+    public List<Metrics> multipleLinearMetrics(final MetricsQuery query, 
String numOfLinear) throws Exception {
+        final URL queryFileUrl = 
Resources.getResource("metrics-multiLines.gql");
+        final String queryString = Resources.readLines(queryFileUrl, 
Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{step}", query.step())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end())
+            .replace("{metricsName}", query.metricsName())
+            .replace("{id}", query.id())
+            .replace("{numOfLinear}", numOfLinear);
+        final ResponseEntity<GQLResponse<MultiMetricsData>> responseEntity = 
restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, 
URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<MultiMetricsData>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + 
responseEntity.getStatusCode());
+        }
+
+        return 
Objects.requireNonNull(responseEntity.getBody()).getData().getMetrics();
+    }
+
 }
diff --git 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
index a78f50f..db085c1 100644
--- 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
+++ 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
@@ -18,24 +18,14 @@
 
 package org.apache.skywalking.e2e.metrics;
 
+import lombok.Data;
+import lombok.ToString;
+
 /**
  * @author kezhenxu94
  */
+@Data
+@ToString
 public class MetricsData {
     private Metrics metrics;
-
-    public Metrics getMetrics() {
-        return metrics;
-    }
-
-    public void setMetrics(Metrics metrics) {
-        this.metrics = metrics;
-    }
-
-    @Override
-    public String toString() {
-        return "MetricsData{" +
-            "metrics=" + metrics +
-            '}';
-    }
 }
diff --git 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsMatcher.java
 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsMatcher.java
index 1e1bed8..06a3ffc 100644
--- 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsMatcher.java
+++ 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsMatcher.java
@@ -18,13 +18,13 @@
 
 package org.apache.skywalking.e2e.metrics;
 
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.List;
 import org.apache.skywalking.e2e.SimpleQueryClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-
 /**
  * @author zhangwei
  */
@@ -32,23 +32,22 @@ public class MetricsMatcher {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(MetricsMatcher.class);
 
-
     public static void verifyMetrics(SimpleQueryClient queryClient, String 
metricName, String id,
-                                        final LocalDateTime minutesAgo) throws 
Exception {
+        final LocalDateTime minutesAgo) throws Exception {
         verifyMetrics(queryClient, metricName, id, minutesAgo, 0, null);
     }
 
     public static void verifyMetrics(SimpleQueryClient queryClient, String 
metricName, String id,
-                                        final LocalDateTime minutesAgo, long 
retryInterval, Runnable generateTraffic) throws Exception {
+        final LocalDateTime minutesAgo, long retryInterval, Runnable 
generateTraffic) throws Exception {
         boolean valid = false;
         while (!valid) {
             Metrics metrics = queryClient.metrics(
-                    new MetricsQuery()
-                            .stepByMinute()
-                            .metricsName(metricName)
-                            .start(minutesAgo)
-                            
.end(LocalDateTime.now(ZoneOffset.UTC).plusMinutes(1))
-                            .id(id)
+                new MetricsQuery()
+                    .stepByMinute()
+                    .metricsName(metricName)
+                    .start(minutesAgo)
+                    .end(LocalDateTime.now(ZoneOffset.UTC).plusMinutes(1))
+                    .id(id)
             );
             LOGGER.info("{}: {}", metricName, metrics);
             AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new 
AtLeastOneOfMetricsMatcher();
@@ -68,4 +67,36 @@ public class MetricsMatcher {
             }
         }
     }
+
+    public static void verifyPercentileMetrics(SimpleQueryClient queryClient, 
String metricName, String id,
+        final LocalDateTime minutesAgo, long retryInterval, Runnable 
generateTraffic) throws Exception {
+        boolean valid = false;
+        while (!valid) {
+            List<Metrics> metricsArray = queryClient.multipleLinearMetrics(
+                new MetricsQuery()
+                    .stepByMinute()
+                    .metricsName(metricName)
+                    .start(minutesAgo)
+                    .end(LocalDateTime.now(ZoneOffset.UTC).plusMinutes(1))
+                    .id(id),
+                "5"
+            );
+            LOGGER.info("{}: {}", metricName, metricsArray);
+            AtLeastOneOfMetricsMatcher matcher = new 
AtLeastOneOfMetricsMatcher();
+            MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+            greaterThanZero.setValue("gt 0");
+            matcher.setValue(greaterThanZero);
+            try {
+                metricsArray.forEach(matcher::verify);
+                valid = true;
+            } catch (Throwable e) {
+                if (generateTraffic != null) {
+                    generateTraffic.run();
+                    Thread.sleep(retryInterval);
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
 }
diff --git 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
index 62c0d76..6b532c9 100644
--- 
a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
+++ 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
@@ -24,32 +24,32 @@ import org.apache.skywalking.e2e.AbstractQuery;
  * @author kezhenxu94
  */
 public class MetricsQuery extends AbstractQuery<MetricsQuery> {
-    public static String SERVICE_P99 = "service_p99";
-    public static String SERVICE_P95 = "service_p95";
-    public static String SERVICE_P90 = "service_p90";
-    public static String SERVICE_P75 = "service_p75";
-    public static String SERVICE_P50 = "service_p50";
+    public static String SERVICE_SLA = "service_sla";
+    public static String SERVICE_CPM = "service_cpm";
+    public static String SERVICE_RESP_TIME = "service_resp_time";
     public static String SERVICE_APDEX = "service_apdex";
     public static String[] ALL_SERVICE_METRICS = {
-        SERVICE_P99,
-        SERVICE_P95,
-        SERVICE_P90,
-        SERVICE_P75,
-        SERVICE_P50,
+        SERVICE_SLA,
+        SERVICE_CPM,
+        SERVICE_RESP_TIME,
         SERVICE_APDEX
     };
+    public static String SERVICE_PERCENTILE = "service_percentile";
+    public static String[] ALL_SERVICE_MULTIPLE_LINEAR_METRICS = {
+        SERVICE_PERCENTILE
+    };
 
-    public static String ENDPOINT_P99 = "endpoint_p99";
-    public static String ENDPOINT_P95 = "endpoint_p95";
-    public static String ENDPOINT_P90 = "endpoint_p90";
-    public static String ENDPOINT_P75 = "endpoint_p75";
-    public static String ENDPOINT_P50 = "endpoint_p50";
+    public static String ENDPOINT_CPM = "endpoint_cpm";
+    public static String ENDPOINT_AVG = "endpoint_avg";
+    public static String ENDPOINT_SLA = "endpoint_sla";
     public static String[] ALL_ENDPOINT_METRICS = {
-        ENDPOINT_P99,
-        ENDPOINT_P95,
-        ENDPOINT_P90,
-        ENDPOINT_P75,
-        ENDPOINT_P50
+        ENDPOINT_CPM,
+        ENDPOINT_AVG,
+        ENDPOINT_SLA,
+    };
+    public static String ENDPOINT_PERCENTILE = "endpoint_percentile";
+    public static String[] ALL_ENDPOINT_MULTIPLE_LINEAR_METRICS = {
+        ENDPOINT_PERCENTILE
     };
 
     public static String SERVICE_INSTANCE_RESP_TIME = 
"service_instance_resp_time";
diff --git 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MultiMetricsData.java
similarity index 78%
copy from 
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
copy to 
test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MultiMetricsData.java
index 04e9564..02ee580 100644
--- 
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/MetricsValueType.java
+++ 
b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MultiMetricsData.java
@@ -16,8 +16,18 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider;
+package org.apache.skywalking.e2e.metrics;
 
-public enum MetricsValueType {
-    LONG, INT, DOUBLE
+import lombok.Data;
+import lombok.ToString;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+@Data
+@ToString
+public class MultiMetricsData {
+    private List<Metrics> metrics;
 }
diff --git a/test/e2e/e2e-base/src/main/resources/metrics-multiLines.gql 
b/test/e2e/e2e-base/src/main/resources/metrics-multiLines.gql
new file mode 100644
index 0000000..cc64e2d
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/metrics-multiLines.gql
@@ -0,0 +1,37 @@
+# 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.
+
+{
+  "query":"query ($id: ID!, $duration: Duration!, $numOfLinear: Int!) {
+    metrics: getMultipleLinearIntValues(metric: {
+      name: \"{metricsName}\"
+      id: $id
+    }, duration: $duration, numOfLinear: $numOfLinear) {
+      values {
+        value
+      }
+    }
+  }",
+  "variables": {
+    "duration": {
+      "start": "{start}",
+      "end": "{end}",
+      "step": "{step}"
+    },
+    "id": "{id}",
+    "numOfLinear": "{numOfLinear}"
+  }
+}
diff --git 
a/test/e2e/e2e-cluster/e2e-cluster-test-runner/src/test/java/org/apache/skywalking/e2e/ClusterVerificationITCase.java
 
b/test/e2e/e2e-cluster/e2e-cluster-test-runner/src/test/java/org/apache/skywalking/e2e/ClusterVerificationITCase.java
index d3fac68..b3fa976 100755
--- 
a/test/e2e/e2e-cluster/e2e-cluster-test-runner/src/test/java/org/apache/skywalking/e2e/ClusterVerificationITCase.java
+++ 
b/test/e2e/e2e-cluster/e2e-cluster-test-runner/src/test/java/org/apache/skywalking/e2e/ClusterVerificationITCase.java
@@ -55,6 +55,7 @@ import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import static org.apache.skywalking.e2e.metrics.MetricsMatcher.verifyMetrics;
+import static 
org.apache.skywalking.e2e.metrics.MetricsMatcher.verifyPercentileMetrics;
 import static org.apache.skywalking.e2e.metrics.MetricsQuery.*;
 import static org.assertj.core.api.Assertions.fail;
 
@@ -293,6 +294,10 @@ public class ClusterVerificationITCase {
 
                 verifyMetrics(queryClient, metricName, endpoint.getKey(), 
minutesAgo, retryInterval, this::generateTraffic);
             }
+
+            for (String metricName : ALL_ENDPOINT_MULTIPLE_LINEAR_METRICS) {
+                verifyPercentileMetrics(queryClient, metricName, 
endpoint.getKey(), minutesAgo, retryInterval, this::generateTraffic);
+            }
         }
     }
 
@@ -302,6 +307,9 @@ public class ClusterVerificationITCase {
 
             verifyMetrics(queryClient, metricName, service.getKey(), 
minutesAgo, retryInterval, this::generateTraffic);
         }
+        for (String metricName : ALL_SERVICE_MULTIPLE_LINEAR_METRICS) {
+            verifyPercentileMetrics(queryClient, metricName, service.getKey(), 
minutesAgo, retryInterval, this::generateTraffic);
+        }
     }
 
     private void verifyTraces(LocalDateTime minutesAgo) throws Exception {
diff --git a/test/e2e/e2e-ttl/e2e-ttl-es/src/docker/ttl_official_analysis.oal 
b/test/e2e/e2e-ttl/e2e-ttl-es/src/docker/ttl_official_analysis.oal
index 52a0ccd..9353579 100755
--- a/test/e2e/e2e-ttl/e2e-ttl-es/src/docker/ttl_official_analysis.oal
+++ b/test/e2e/e2e-ttl/e2e-ttl-es/src/docker/ttl_official_analysis.oal
@@ -20,4 +20,4 @@
 // In TTL ES7 testing, using full oal config makes the test very unstable, so 
only service_p99 is reserved for testing.
 
 // Service scope metrics
-service_p99 = from(Service.latency).p99(10);
\ No newline at end of file
+service_resp_time = from(Service.latency).longAvg();
\ No newline at end of file
diff --git 
a/test/e2e/e2e-ttl/e2e-ttl-es/src/test/java/org/apache/skywalking/e2e/StorageTTLITCase.java
 
b/test/e2e/e2e-ttl/e2e-ttl-es/src/test/java/org/apache/skywalking/e2e/StorageTTLITCase.java
index ecd778c..4901ccc 100644
--- 
a/test/e2e/e2e-ttl/e2e-ttl-es/src/test/java/org/apache/skywalking/e2e/StorageTTLITCase.java
+++ 
b/test/e2e/e2e-ttl/e2e-ttl-es/src/test/java/org/apache/skywalking/e2e/StorageTTLITCase.java
@@ -43,7 +43,7 @@ import java.time.ZoneOffset;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
-import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P99;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_RESP_TIME;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
@@ -243,7 +243,7 @@ public class StorageTTLITCase {
                 return queryClient.metrics(
                     new MetricsQuery()
                         .id(serviceId)
-                        .metricsName(SERVICE_P99)
+                        .metricsName(SERVICE_RESP_TIME)
                         .step(step)
                         .start(queryStart)
                         .end(queryEnd)
diff --git a/test/e2e/run.sh b/test/e2e/run.sh
index 97bdac1..bec5e4a 100755
--- a/test/e2e/run.sh
+++ b/test/e2e/run.sh
@@ -16,7 +16,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-export MAVEN_OPTS='-Dmaven.repo.local=.m2/repository -XX:+TieredCompilation 
-XX:TieredStopAtLevel=1 -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC 
-XX:-UseGCOverheadLimit -Xmx3g'
+export MAVEN_OPTS='-XX:+TieredCompilation -XX:TieredStopAtLevel=1 
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:-UseGCOverheadLimit 
-Xmx3g'
 
 base_dir=$(pwd)
 build=0

Reply via email to