You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2020/03/06 12:48:36 UTC

[skywalking-cli] branch master updated: [Feature] Support multiple linear metrics and enhance Ascii Graph Display (#30)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6542344  [Feature] Support multiple linear metrics and enhance Ascii Graph Display (#30)
6542344 is described below

commit 6542344a89605f04fa32471da4bd5575df677bee
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Fri Mar 6 20:48:25 2020 +0800

    [Feature] Support multiple linear metrics and enhance Ascii Graph Display (#30)
    
    ### Motivation
    
    Adopt the new API of multiple linear metrics, https://github.com/apache/skywalking/pull/4214
    
    ### Modification
    
    - Rewrite the pxx metrics with the new API
    
    - Slightly refactor the GraphQL client into individual package, according to the query protocol
    
    - Enhance the Ascii Graph display style
    
    ### Result
    
    - CLI is compatible with the latest backend
    
    - Ascii Graph can display multiple charts in one screen
---
 .golangci.yml                                      |   2 +-
 Makefile                                           |   5 +-
 README.md                                          | 101 ++++++++++++--
 commands/endpoint/list.go                          |   5 +-
 commands/instance/instance.go                      |   5 +-
 commands/instance/list.go                          |   5 +-
 commands/instance/search.go                        |   5 +-
 commands/interceptor/duration.go                   |  12 +-
 commands/metrics/linear/linear-metrics.go          |  22 +--
 ...inear-metrics.go => multiple-linear-metrics.go} |  39 ++++--
 .../graph/graph.go => commands/metrics/metrics.go  |  24 ++--
 commands/metrics/single/single-metrics.go          |   9 +-
 commands/service/list.go                           |   7 +-
 display/display.go                                 |   8 +-
 display/graph/graph.go                             |  14 +-
 display/graph/linear/linear.go                     |  82 +++++++----
 graphql/client/client.go                           | 150 +--------------------
 graphql/metadata/metadata.go                       |  93 ++++++++++++-
 graphql/metrics/metrics.go                         |  82 +++++++++++
 graphql/{metadata/metadata.go => utils/adapter.go} |  37 ++---
 graphql/{schema => utils}/constants.go             |  32 +++--
 swctl/main.go                                      |  12 +-
 22 files changed, 468 insertions(+), 283 deletions(-)

diff --git a/.golangci.yml b/.golangci.yml
index cebf66b..26b94e2 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -38,7 +38,7 @@ linters-settings:
   misspell:
     locale: US
   lll:
-    line-length: 120
+    line-length: 150
   goimports:
     local-prefixes: github.com/apache/skywalking-cli
   gocritic:
diff --git a/Makefile b/Makefile
index 8ddb4eb..885df84 100644
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ $(PLATFORMS):
 	GOOS=$(os) GOARCH=$(ARCH) $(GO_BUILD) $(GO_BUILD_FLAGS) -ldflags "$(GO_BUILD_LDFLAGS)" -o $(OUT_DIR)/$(BINARY)-$(VERSION)-$(os)-$(ARCH) swctl/main.go
 
 .PHONY: lint
-lint:
+lint: codegen
 	$(GO_LINT) version || curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin v1.21.0
 	$(GO_LINT) run -v ./...
 
@@ -86,7 +86,7 @@ coverage: test
 	bash <(curl -s https://codecov.io/bash) -t a5af28a3-92a2-4b35-9a77-54ad99b1ae00
 
 .PHONY: clean
-clean:
+clean: codegen
 	$(GO_CLEAN) ./...
 	-rm -rf bin
 	-rm -rf coverage.txt
@@ -94,6 +94,7 @@ clean:
 	-rm -rf *.tgz
 	-rm -rf *.asc
 	-rm -rf *.sha512
+	-rm -rf query-protocol/schema.graphqls
 
 release-src: clean
 	-tar -zcvf $(RELEASE_SRC).tgz \
diff --git a/README.md b/README.md
index 00f225c..0467d09 100644
--- a/README.md
+++ b/README.md
@@ -149,11 +149,13 @@ This section covers all the available commands in SkyWalking CLI and their usage
 
 </details>
 
-### `linear-metrics`
+### `metrics`
+
+#### `metrics linear`
 
 <details>
 
-<summary>linear-metrics [--start=start-time] [--end=end-time] --name=metrics-name [--id=entity-id]</summary>
+<summary>metrics linear [--start=start-time] [--end=end-time] --name=metrics-name [--id=entity-id]</summary>
 
 | option | description | default |
 | :--- | :--- | :--- |
@@ -164,11 +166,27 @@ This section covers all the available commands in SkyWalking CLI and their usage
 
 </details>
 
-### `single-metrics`
+#### `metrics multiple-linear`
 
 <details>
 
-<summary>single-metrics [--start=start-time] [--end=end-time] --name=metrics-name [--ids=entity-ids]</summary>
+<summary>metrics multiple-linear [--start=start-time] [--end=end-time] --name=metrics-name [--id=entity-id] [--num=number-of-linear-metrics]</summary>
+
+| option | description | default |
+| :--- | :--- | :--- |
+| `--name` | Metrics name, defined in [OAL](https://github.com/apache/skywalking/blob/master/oap-server/server-bootstrap/src/main/resources/official_analysis.oal), such as `all_p99`, etc. |
+| `--id` | the related id if the metrics requires one, e.g. for metrics `service_p99`, the service `id` is required, use `--id` to specify the service id, the same for `instance`, `endpoint`, etc. |
+| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--num` | Number of the linear metrics to fetch | `5` |
+
+</details>
+
+#### `metrics single`
+
+<details>
+
+<summary>metrics single [--start=start-time] [--end=end-time] --name=metrics-name [--ids=entity-ids]</summary>
 
 | option | description | default |
 | :--- | :--- | :--- |
@@ -239,7 +257,7 @@ otherwise,
 If you have already got the `id` of the instance:
 
 ```shell
-$ ./bin/swctl --display=graph linear-metrics --name=service_instance_resp_time --id 5
+$ ./bin/swctl --display=graph metrics linear --name=service_instance_resp_time --id 5
 ┌─────────────────────────────────────────────────────────────────────────────────Press q to quit──────────────────────────────────────────────────────────────────────────────────┐
 │                                                                                                                                                                                  │
 │                                                                                                                                                                                  │
@@ -263,7 +281,7 @@ $ ./bin/swctl --display=graph linear-metrics --name=service_instance_resp_time -
 otherwise
 
 ```shell
-$ ./bin/swctl instance ls --service-name=projectC | jq '.[] | select(.name == "projectC-pid:7895@skywalking-server-0001").id' | xargs ./bin/swctl --display=graph linear-metrics --name=service_instance_resp_time --id
+$ ./bin/swctl instance ls --service-name=projectC | jq '.[] | select(.name == "projectC-pid:7895@skywalking-server-0001").id' | xargs ./bin/swctl --display=graph metrics linear --name=service_instance_resp_time --id
 ┌─────────────────────────────────────────────────────────────────────────────────Press q to quit──────────────────────────────────────────────────────────────────────────────────┐
 │                                                                                                                                                                                  │
 │                                                                                                                                                                                  │
@@ -291,7 +309,7 @@ $ ./bin/swctl instance ls --service-name=projectC | jq '.[] | select(.name == "p
 <summary>Query a single metrics value for a specific endpoint</summary>
 
 ```shell
-$ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls --service-id | jq '.[] | [.id] | join(",")' | xargs ./bin/swctl single-metrics --name endpoint_cpm --ids
+$ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls --service-id | jq '.[] | [.id] | join(",")' | xargs ./bin/swctl metrics single --name endpoint_cpm --ids
 [{"id":"22","value":116}]
 ```
 
@@ -302,7 +320,7 @@ $ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls
 <summary>Query metrics single values for all endpoints of service of id 3</summary>
 
 ```shell
-$ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls --service-id | jq '.[] | [.id] | join(",")' | xargs ./bin/swctl single-metrics --name endpoint_cpm --end='2019-12-02 2137' --ids
+$ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls --service-id | jq '.[] | [.id] | join(",")' | xargs ./bin/swctl metrics single --name endpoint_cpm --end='2019-12-02 2137' --ids
 [{"id":"3","value":116}]
 ```
 
@@ -310,6 +328,73 @@ $ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls
 
 <details>
 
+<summary>Query multiple metrics values for all percentiles</summary>
+
+```shell
+$ ./bin/swctl-latest-darwin-amd64 --display=graph --debug metrics multiple-linear --name all_percentile
+
+┌PRESS Q TO QUIT───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
+│┌───────────────────────────────#0───────────────────────────────┐┌───────────────────────────────#1───────────────────────────────┐┌─────────────────────────────────#2─────────────────────────────────┐│
+││      │  ⡏⠉⠉⢹   ⢸⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⡇      ⢸⠉⠉⠉⠉⠉⠉⠉⡇  ⢸⠉⠉⠉⠉⠉⠉⠉⡇   ⡏⠉⠉⠉ ││       │     ⢸⡀                       ⢸        ⢸        ⡇       ││        │                                                  ⡠⠔⡇      ││
+││960.80│ ⢀⠇  ⠘⡄  ⡜            ⢣      ⢸       ⢇  ⢸       ⡇   ⡇    ││1963.60│     ⡜⡇                       ⢸        ⢸       ⢠⡇       ││ 2600.40│                                                  ⡇ ⢣      ││
+││      │ ⢸    ⡇  ⡇            ⢸      ⢸       ⢸  ⡜       ⢸  ⢸     ││       │     ⡇⢸                       ⡼⡀       ⣾       ⢸⢣       ││        │                                                 ⢸  ⢸      ││
+││      │ ⢸    ⡇  ⡇            ⢸      ⡸       ⢸  ⡇       ⢸  ⢸     ││       │     ⡇⠈⡆                      ⡇⡇       ⡇⡇      ⢸⢸       ││        │                                                 ⢸  ⢸      ││
+││      │ ⢸    ⢣ ⢠⠃            ⠘⡄     ⡇       ⢸  ⡇       ⢸  ⢸     ││       │    ⢰⠁ ⡇                      ⡇⡇  ⡤⢤   ⡇⡇      ⡇⢸       ││        │                                                 ⡇  ⠘⡄     ││
+││824.64│ ⡇    ⢸ ⢸              ⡇     ⡇       ⠈⡆ ⡇       ⠘⡄ ⡜     ││1832.88│    ⢸  ⢣                      ⡇⡇  ⡇⢸   ⡇⡇      ⡇⢸       ││ 2486.33│                                                 ⡇   ⡇     ││
+││      │ ⡇    ⢸ ⢸              ⡇     ⡇        ⡇ ⡇        ⡇ ⡇     ││       │    ⢸  ⢸                      ⡇⡇ ⢸ ⠈⡆ ⢀⠇⡇     ⢠⠃⢸       ││        │                                                ⢰⠁   ⡇     ││
+││      │ ⡇    ⠈⡆⡎              ⢣     ⡇        ⡇⢸         ⡇ ⡇     ││       │    ⡎  ⢸                     ⢰⠁⡇ ⢸  ⡇ ⢸ ⡇     ⢸ ⠘⡄      ││        │                       ⡀        ⢸⠉⠲⡀  ⢀         ⢸    ⢱     ││
+││      │⢰⠁     ⡇⡇              ⢸     ⡇        ⢇⢸         ⡇ ⡇     ││       │    ⡇  ⢸                     ⢸ ⢱ ⢸  ⡇ ⢸ ⢣     ⢸  ⡇      ││        │⡀                     ⢰⢱    ⢀⡄  ⡇  ⢱ ⢀⠎⡆        ⡎    ⢸  ⣀⠤ ││
+││688.48│⢸      ⡇⡇              ⢸     ⡇        ⢸⢸         ⢸⢸      ││1702.16│    ⡇   ⡇                    ⢸ ⢸ ⡇  ⢣ ⢸ ⢸     ⡜  ⡇      ││ 2372.24│⠱⡀       ⡴⡀  ⢀       ⢠⠃⠈⡆  ⢀⠎⠸⡀⢠⠃   ⢣⠎ ⢸  ⣠    ⡠⠃    ⢸ ⢰⠁  ││
+││      │⢸      ⢱⠁              ⠘⡄    ⡇        ⢸⢸         ⢸⢸      ││       │   ⢸    ⡇                    ⢸ ⢸ ⡇  ⢸ ⢸ ⢸     ⡇  ⡇      ││        │ ⢣      ⡜ ⠱⡀⡠⠋⡆     ⣀⠎  ⢱ ⡠⠊  ⢣⢸        ⢇⡔⠁⢣ ⣀⠔⠁     ⠈⣦⠃   ││
+││      │⡜      ⠸                ⡇   ⢸         ⢸⡜         ⢸⢸      ││       │   ⢸    ⡇       ⡆     ⢀⡆     ⢸ ⢸⢀⠇  ⢸ ⡎ ⢸     ⡇  ⡇      ││        │  ⡇   ⡔⠊   ⠑⠁ ⠸⡀  ⢠⠋    ⠈⠖⠁   ⠈⠇        ⠈   ⠉         ⠏    ││
+││      │⡇                       ⢣   ⢸         ⠈⡇         ⠘⡜      ││       │   ⡜    ⢱      ⢠⢣  ⢰⢄ ⡜⢸     ⡇ ⢸⢸   ⢸ ⡇ ⢸    ⢠⠃  ⢱      ││        │  ⢇   ⡇        ⢣⡀ ⡎                                        ││
+││552.32│⠁                       ⠸⡀  ⢸          ⡇          ⡇      ││1571.44│   ⡇    ⢸      ⢸⢸  ⡸ ⠙ ⠘⡄    ⡇ ⠘⣼    ⡇⡇ ⢸    ⢸   ⢸      ││ 2258.16│  ⢸  ⢸          ⠈⠙                                         ││
+││      │                         ⢇  ⢸                     ⠁      ││       │  ⢀⠇    ⢸      ⡜⢸  ⡇    ⢇    ⡇  ⡿    ⡇⡇  ⡇   ⢸   ⢸      ││        │  ⢸  ⢸                                                     ││
+││      │                         ⢸  ⢸                            ││       │⢣ ⢸     ⠸⡀     ⡇ ⡇ ⡇    ⢸    ⡇  ⡇    ⣇⠇  ⡇   ⡜   ⢸      ││        │  ⠈⡆ ⡜                                                     ││
+││      │                          ⡇ ⢸                            ││       │⠈⢆⡸      ⡇⢀   ⢠⠃ ⡇⢀⠇    ⠈⡦⠔⢇⢀⠇  ⠁    ⢹   ⡇   ⡇   ⢸      ││        │   ⡇ ⡇                                                     ││
+││416.16│                          ⢱ ⢸                            ││1440.72│ ⠘⡇      ⠋⠙⡄  ⢸  ⢱⢸        ⠸⣸        ⢸   ⠱⡀  ⡇   ⠈⡆     ││2144.080│   ⡇ ⡇                                                     ││
+││      │                          ⠘⡄⡎                            ││       │           ⢇  ⡎  ⢸⢸         ⢿             ⠱⡀⢠⠃    ⡇     ││        │   ⢸⢸                                                      ││
+││      │                           ⡇⡇                            ││       │           ⢸ ⢰⠁  ⠸⡜         ⠈              ⠘⣼     ⠧⣀    ││        │   ⢸⢸                                                      ││
+││      │                           ⢸⡇                            ││       │            ⡇⡎    ⡇                         ⠈       ⠑⢄  ││        │   ⠘⡜                                                      ││
+││   280│                           ⠈⡇                            ││   1310│            ⢱⠁                                          ││    2030│    ⡇                                                      ││
+││      └─────────────────────────────────────────────────────────││       └────────────────────────────────────────────────────────││        └───────────────────────────────────────────────────────────││
+││       2020-03-07 0111   2020-03-07 0134   2020-03-07 0133      ││        2020-03-07 0116   2020-03-07 0121   2020-03-07 0122     ││         2020-03-07 0123   2020-03-07 0139   2020-03-07 0117        ││
+│└────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────┘│
+│┌────────────────────────────────────────────────#3─────────────────────────────────────────────────┐┌────────────────────────────────────────────────#4─────────────────────────────────────────────────┐│
+││       │                                           ⢀⢇                                              ││        │⠤⠤⠤⠤⠤⠤⡄     ⡤⠤⢤        ⢸⠑⠒⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠒⠊⠉⠉⠉⠉⠉⠉⠉⠒⠢⡄     ⡤⠒⠊⡇       ⢠⠔⠒⢹           ⢠⠔⠒⠉⠑⠢⠄ ││
+││       │                                           ⡸⠸⡀               ⢀⡆                            ││        │      ⡇     ⡇ ⢸        ⡸                          ⡇     ⡇  ⢇       ⢸  ⢸           ⢸       ││
+││3559.60│                                          ⢀⠇ ⢇              ⢀⠎⢸                            ││54073.20│      ⢱    ⢰⠁ ⠈⡆       ⡇                          ⢱    ⢰⠁  ⢸       ⡜   ⡇          ⡎       ││
+││       │           ⢀⢄                             ⡸  ⠸⡀            ⢀⠎ ⠘⡄                           ││        │      ⢸    ⢸   ⡇       ⡇                          ⢸    ⢸   ⢸       ⡇   ⡇          ⡇       ││
+││       │          ⢀⠎ ⠑⢄                          ⢀⠇   ⢇           ⢀⠎   ⡇         ⣼                 ││        │      ⢸    ⢸   ⡇       ⡇                          ⢸    ⢸   ⢸       ⡇   ⡇          ⡇       ││
+││       │         ⢀⠎   ⠈⢆                      ⣀  ⡸    ⠸⡀ ⣀⡀       ⡜    ⢸        ⡸⠸⡀                ││        │      ⠸⡀   ⡸   ⢇      ⢰⠁                          ⠸⡀   ⡸   ⠈⡆      ⡇   ⢣         ⢀⠇       ││
+││3325.68│   ⣀⣀  ⣀⠤⠊     ⠘⡄   ⢀⣀⣀⣀⣀⡠⠤⡀      ⢀⣀⠔⠊ ⠉⠑⠃     ⠉⠉ ⠘⢄     ⡰⠁    ⠘⡄      ⢰⠁ ⡇       ⢀⣀⡠⠤⠤⠤⠄  ││43924.56│       ⡇   ⡇   ⢸      ⢸                            ⡇   ⡇    ⡇     ⢰⠁   ⢸         ⢸        ││
+││       │ ⢠⠊  ⠉⠉         ⠸⡀ ⡔⠁      ⠑⢄  ⡠⠊⠉⠁                 ⠣⣀  ⢠⠃      ⡇     ⢠⠃  ⡇    ⢀⠤⠊⠁        ││        │       ⡇   ⡇   ⢸      ⢸                            ⡇   ⡇    ⡇     ⢸    ⢸         ⢸        ││
+││       │⠔⠁               ⠱⠊         ⠈⠢⠊                       ⠉⠒⠎       ⠸⠤⠤⠤⠔⠊⠁   ⢇    ⢸           ││        │       ⡇   ⡇   ⢸      ⡸                            ⡇   ⡇    ⢇     ⢸    ⢸         ⢸        ││
+││       │                                                                          ⢸    ⡎           ││        │       ⢱  ⢰⠁   ⠈⡆     ⡇                            ⢸  ⢸     ⢸     ⢸     ⡇        ⡎        ││
+││3091.76│                                                                          ⢸    ⡇           ││33775.92│       ⢸  ⢸     ⡇     ⡇                            ⢸  ⢸     ⢸     ⡇     ⡇        ⡇        ││
+││       │                                                                          ⢸   ⢀⠇           ││        │       ⢸  ⢸     ⡇     ⡇                            ⢸  ⢸     ⢸     ⡇     ⡇        ⡇        ││
+││       │                                                                           ⡇  ⢸            ││        │       ⠸⡀ ⡸     ⢇    ⢰⠁                            ⠘⡄ ⡜     ⠈⡆    ⡇     ⢣       ⢠⠃        ││
+││       │                                                                           ⡇  ⢸            ││        │        ⡇ ⡇     ⢸    ⢸                              ⡇ ⡇      ⡇   ⢠⠃     ⢸       ⢸         ││
+││2857.84│                                                                           ⡇  ⡎            ││23627.28│        ⡇ ⡇     ⢸    ⢸                              ⡇ ⡇      ⡇   ⢸      ⢸       ⢸         ││
+││       │                                                                           ⢸  ⡇            ││        │        ⡇ ⡇     ⢸    ⡸                              ⢇⢀⠇      ⢇   ⢸      ⢸       ⢸         ││
+││       │                                                                           ⢸ ⢀⠇            ││        │        ⢱⢰⠁     ⠈⡆   ⡇                              ⢸⢸       ⢸   ⢸       ⡇      ⡇         ││
+││       │                                                                           ⢸ ⢸             ││        │        ⢸⢸       ⡇   ⡇                              ⢸⢸       ⢸   ⡎       ⡇      ⡇         ││
+││2623.92│                                                                           ⠈⡆⢸             ││13478.64│        ⢸⢸       ⡇   ⡇                              ⢸⢸       ⢸   ⡇       ⡇      ⡇         ││
+││       │                                                                            ⡇⡎             ││        │        ⠸⡸       ⢇  ⢰⠁                              ⠈⡎       ⠈⡆  ⡇       ⢣     ⢠⠃         ││
+││       │                                                                            ⡇⡇             ││        │         ⡇       ⢸  ⣸                                ⡇        ⡇  ⡇       ⢸     ⢸          ││
+││       │                                                                            ⢱⠇             ││        │         ⠃       ⠘⠊⠉                                          ⠘⡄⢸        ⠘⠒⠊⠉⠉⠉⠉          ││
+││   2390│                                                                            ⢸              ││    3330│                                                               ⠈⢾                         ││
+││       └───────────────────────────────────────────────────────────────────────────────────────────││        └──────────────────────────────────────────────────────────────────────────────────────────││
+││        2020-03-07 0115   2020-03-07 0139   2020-03-07 0134   2020-03-07 0136   2020-03-07 0132    ││         2020-03-07 0115   2020-03-07 0126   2020-03-07 0112   2020-03-07 0134   2020-03-07 0124   ││
+│└───────────────────────────────────────────────────────────────────────────────────────────────────┘└───────────────────────────────────────────────────────────────────────────────────────────────────┘│
+└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+```
+
+</details>
+
+<details>
+
 <summary>Automatically convert to server side timezone</summary>
 
 if your backend nodes are deployed in docker and the timezone is UTC, you may not want to convert your timezone to UTC every time you type a command, `--timezone` comes to your rescue.
diff --git a/commands/endpoint/list.go b/commands/endpoint/list.go
index 51ea320..034c6a5 100644
--- a/commands/endpoint/list.go
+++ b/commands/endpoint/list.go
@@ -20,8 +20,9 @@ package endpoint
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/graphql/metadata"
+
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 )
 
 var ListCommand = cli.Command{
@@ -53,7 +54,7 @@ var ListCommand = cli.Command{
 		limit := ctx.Int("limit")
 		keyword := ctx.String("keyword")
 
-		endpoints := client.SearchEndpoints(ctx, serviceID, keyword, limit)
+		endpoints := metadata.SearchEndpoints(ctx, serviceID, keyword, limit)
 
 		return display.Display(ctx, endpoints)
 	},
diff --git a/commands/instance/instance.go b/commands/instance/instance.go
index da83c80..29c86a7 100644
--- a/commands/instance/instance.go
+++ b/commands/instance/instance.go
@@ -20,7 +20,8 @@ package instance
 import (
 	"github.com/urfave/cli"
 
-	"github.com/apache/skywalking-cli/graphql/client"
+	"github.com/apache/skywalking-cli/graphql/metadata"
+
 	"github.com/apache/skywalking-cli/logger"
 )
 
@@ -43,7 +44,7 @@ func verifyAndSwitchServiceParameter(ctx *cli.Context) string {
 	}
 
 	if serviceID == "" && serviceName != "" {
-		service, err := client.SearchService(ctx, serviceName)
+		service, err := metadata.SearchService(ctx, serviceName)
 		if err != nil {
 			logger.Log.Fatalln(err)
 		}
diff --git a/commands/instance/list.go b/commands/instance/list.go
index c20ccb4..eda72c1 100644
--- a/commands/instance/list.go
+++ b/commands/instance/list.go
@@ -20,11 +20,12 @@ package instance
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/graphql/metadata"
+
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
@@ -44,7 +45,7 @@ var ListCommand = cli.Command{
 		start := ctx.String("start")
 		step := ctx.Generic("step")
 
-		instances := client.Instances(ctx, serviceID, schema.Duration{
+		instances := metadata.Instances(ctx, serviceID, schema.Duration{
 			Start: start,
 			End:   end,
 			Step:  step.(*model.StepEnumValue).Selected,
diff --git a/commands/instance/search.go b/commands/instance/search.go
index b9e2ac9..b97adae 100644
--- a/commands/instance/search.go
+++ b/commands/instance/search.go
@@ -20,13 +20,14 @@ package instance
 import (
 	"regexp"
 
+	"github.com/apache/skywalking-cli/graphql/metadata"
+
 	"github.com/urfave/cli"
 
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
@@ -47,7 +48,7 @@ var SearchCommand = cli.Command{
 
 		regex := ctx.String("regex")
 
-		instances := client.Instances(ctx, serviceID, schema.Duration{
+		instances := metadata.Instances(ctx, serviceID, schema.Duration{
 			Start: start,
 			End:   end,
 			Step:  step.(*model.StepEnumValue).Selected,
diff --git a/commands/interceptor/duration.go b/commands/interceptor/duration.go
index 3eb355a..c52ae69 100644
--- a/commands/interceptor/duration.go
+++ b/commands/interceptor/duration.go
@@ -21,6 +21,8 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/apache/skywalking-cli/graphql/utils"
+
 	"github.com/urfave/cli"
 
 	"github.com/apache/skywalking-cli/graphql/schema"
@@ -29,7 +31,7 @@ import (
 
 func tryParseTime(unparsed string) (schema.Step, time.Time, error) {
 	var possibleError error = nil
-	for step, layout := range schema.StepFormats {
+	for step, layout := range utils.StepFormats {
 		t, err := time.Parse(layout, unparsed)
 		if err == nil {
 			return step, t, nil
@@ -48,9 +50,9 @@ func DurationInterceptor(ctx *cli.Context) error {
 
 	startTime, endTime, step := ParseDuration(start, end, timezone)
 
-	if err := ctx.Set("start", startTime.Format(schema.StepFormats[step])); err != nil {
+	if err := ctx.Set("start", startTime.Format(utils.StepFormats[step])); err != nil {
 		return err
-	} else if err := ctx.Set("end", endTime.Format(schema.StepFormats[step])); err != nil {
+	} else if err := ctx.Set("end", endTime.Format(utils.StepFormats[step])); err != nil {
 		return err
 	} else if err := ctx.Set("step", step.String()); err != nil {
 		return err
@@ -103,12 +105,12 @@ func ParseDuration(start, end, timezone string) (startTime, endTime time.Time, s
 		if step, startTime, err = tryParseTime(start); err != nil {
 			logger.Log.Fatalln("Unsupported time format:", start, err)
 		}
-		return startTime, startTime.Add(30 * schema.StepDuration[step]), step
+		return startTime, startTime.Add(30 * utils.StepDuration[step]), step
 	} else { // start is absent
 		if step, endTime, err = tryParseTime(end); err != nil {
 			logger.Log.Fatalln("Unsupported time format:", end, err)
 		}
-		return endTime.Add(-30 * schema.StepDuration[step]), endTime, step
+		return endTime.Add(-30 * utils.StepDuration[step]), endTime, step
 	}
 }
 
diff --git a/commands/metrics/linear/linear-metrics.go b/commands/metrics/linear/linear-metrics.go
index ceda447..d435063 100644
--- a/commands/metrics/linear/linear-metrics.go
+++ b/commands/metrics/linear/linear-metrics.go
@@ -20,16 +20,18 @@ package linear
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/graphql/metrics"
+	"github.com/apache/skywalking-cli/graphql/utils"
+
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
-var Command = cli.Command{
-	Name:  "linear-metrics",
+var Single = cli.Command{
+	Name:  "linear",
 	Usage: "Query linear metrics defined in backend OAL",
 	Flags: flags.Flags(
 		flags.DurationFlags,
@@ -62,15 +64,17 @@ var Command = cli.Command{
 			id = &idString
 		}
 
-		metricsValues := client.LinearIntValues(ctx, schema.MetricCondition{
-			Name: metricsName,
-			ID:   id,
-		}, schema.Duration{
+		duration := schema.Duration{
 			Start: start,
 			End:   end,
 			Step:  step.(*model.StepEnumValue).Selected,
-		})
+		}
+
+		metricsValues := metrics.LinearIntValues(ctx, schema.MetricCondition{
+			Name: metricsName,
+			ID:   id,
+		}, duration)
 
-		return display.Display(ctx, metricsValues)
+		return display.Display(ctx, utils.MetricsToMap(duration, metricsValues))
 	},
 }
diff --git a/commands/metrics/linear/linear-metrics.go b/commands/metrics/linear/multiple-linear-metrics.go
similarity index 70%
copy from commands/metrics/linear/linear-metrics.go
copy to commands/metrics/linear/multiple-linear-metrics.go
index ceda447..31db251 100644
--- a/commands/metrics/linear/linear-metrics.go
+++ b/commands/metrics/linear/multiple-linear-metrics.go
@@ -20,23 +20,25 @@ package linear
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/graphql/metrics"
+	"github.com/apache/skywalking-cli/graphql/utils"
+
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
-var Command = cli.Command{
-	Name:  "linear-metrics",
-	Usage: "Query linear metrics defined in backend OAL",
+var Multiple = cli.Command{
+	Name:  "multiple-linear",
+	Usage: "Query multiple linear metrics defined in backend OAL",
 	Flags: flags.Flags(
 		flags.DurationFlags,
 		[]cli.Flag{
 			cli.StringFlag{
 				Name:     "name",
-				Usage:    "metrics `NAME`, such as `all_p99`",
+				Usage:    "metrics `NAME`, such as `all_percentile`",
 				Required: true,
 			},
 			cli.StringFlag{
@@ -44,6 +46,12 @@ var Command = cli.Command{
 				Usage:    "`ID`, the related id if the metrics requires one",
 				Required: false,
 			},
+			cli.IntFlag{
+				Name:     "num",
+				Usage:    "`num`, the number of linear metrics to query, (default: 5)",
+				Required: false,
+				Value:    5,
+			},
 		},
 	),
 	Before: interceptor.BeforeChain([]cli.BeforeFunc{
@@ -55,6 +63,7 @@ var Command = cli.Command{
 		start := ctx.String("start")
 		step := ctx.Generic("step")
 		metricsName := ctx.String("name")
+		numOfLinear := ctx.Int("num")
 
 		var id *string = nil
 
@@ -62,15 +71,23 @@ var Command = cli.Command{
 			id = &idString
 		}
 
-		metricsValues := client.LinearIntValues(ctx, schema.MetricCondition{
-			Name: metricsName,
-			ID:   id,
-		}, schema.Duration{
+		duration := schema.Duration{
 			Start: start,
 			End:   end,
 			Step:  step.(*model.StepEnumValue).Selected,
-		})
+		}
+
+		values := metrics.MultipleLinearIntValues(ctx, schema.MetricCondition{
+			Name: metricsName,
+			ID:   id,
+		}, numOfLinear, duration)
+
+		reshaped := make([]map[string]float64, len(values))
+
+		for index, value := range values {
+			reshaped[index] = utils.MetricsToMap(duration, value)
+		}
 
-		return display.Display(ctx, metricsValues)
+		return display.Display(ctx, reshaped)
 	},
 }
diff --git a/display/graph/graph.go b/commands/metrics/metrics.go
similarity index 69%
copy from display/graph/graph.go
copy to commands/metrics/metrics.go
index 7184360..8f941b9 100644
--- a/display/graph/graph.go
+++ b/commands/metrics/metrics.go
@@ -15,21 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package graph
+package metrics
 
 import (
-	"fmt"
-	"reflect"
+	"github.com/urfave/cli"
 
-	"github.com/apache/skywalking-cli/display/graph/linear"
+	"github.com/apache/skywalking-cli/commands/metrics/linear"
+	"github.com/apache/skywalking-cli/commands/metrics/single"
 )
 
-func Display(object interface{}) error {
-	if reflect.TypeOf(object) != reflect.TypeOf(map[string]float64{}) {
-		return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(object))
-	}
-
-	kvs := object.(map[string]float64)
-
-	return linear.Display(kvs)
+var Command = cli.Command{
+	Name:  "metrics",
+	Usage: "Query metrics defined in backend OAL",
+	Subcommands: cli.Commands{
+		single.Command,
+		linear.Single,
+		linear.Multiple,
+	},
 }
diff --git a/commands/metrics/single/single-metrics.go b/commands/metrics/single/single-metrics.go
index 99817da..69405ea 100644
--- a/commands/metrics/single/single-metrics.go
+++ b/commands/metrics/single/single-metrics.go
@@ -20,18 +20,19 @@ package single
 import (
 	"strings"
 
+	"github.com/apache/skywalking-cli/graphql/metrics"
+
 	"github.com/urfave/cli"
 
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
 var Command = cli.Command{
-	Name:  "single-metrics",
+	Name:  "single",
 	Usage: "Query single metrics defined in backend OAL",
 	Flags: flags.Flags(
 		flags.DurationFlags,
@@ -65,7 +66,7 @@ var Command = cli.Command{
 			ids = append(ids, strings.Split(id, ",")...)
 		}
 
-		metricsValues := client.IntValues(ctx, schema.BatchMetricConditions{
+		metricsValues := metrics.IntValues(ctx, schema.BatchMetricConditions{
 			Name: metricsName,
 			Ids:  ids,
 		}, schema.Duration{
@@ -74,6 +75,6 @@ var Command = cli.Command{
 			Step:  step.(*model.StepEnumValue).Selected,
 		})
 
-		return display.Display(ctx, metricsValues)
+		return display.Display(ctx, metricsValues.Values)
 	},
 }
diff --git a/commands/service/list.go b/commands/service/list.go
index 8400a47..2604a6f 100644
--- a/commands/service/list.go
+++ b/commands/service/list.go
@@ -20,11 +20,12 @@ package service
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/graphql/metadata"
+
 	"github.com/apache/skywalking-cli/commands/flags"
 	"github.com/apache/skywalking-cli/commands/interceptor"
 	"github.com/apache/skywalking-cli/commands/model"
 	"github.com/apache/skywalking-cli/display"
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
@@ -47,13 +48,13 @@ var ListCommand = cli.Command{
 		var services []schema.Service
 
 		if args := ctx.Args(); len(args) == 0 {
-			services = client.Services(ctx, schema.Duration{
+			services = metadata.AllServices(ctx, schema.Duration{
 				Start: start,
 				End:   end,
 				Step:  step.(*model.StepEnumValue).Selected,
 			})
 		} else {
-			service, _ := client.SearchService(ctx, args.First())
+			service, _ := metadata.SearchService(ctx, args.First())
 			services = []schema.Service{service}
 		}
 
diff --git a/display/display.go b/display/display.go
index 48be6ab..e620673 100644
--- a/display/display.go
+++ b/display/display.go
@@ -31,10 +31,10 @@ import (
 )
 
 const (
-	JSON  string = "json"
-	YAML  string = "yaml"
-	TABLE string = "table"
-	GRAPH string = "graph"
+	JSON  = "json"
+	YAML  = "yaml"
+	TABLE = "table"
+	GRAPH = "graph"
 )
 
 // Display the object in the style specified in flag --display
diff --git a/display/graph/graph.go b/display/graph/graph.go
index 7184360..c27f835 100644
--- a/display/graph/graph.go
+++ b/display/graph/graph.go
@@ -25,11 +25,17 @@ import (
 )
 
 func Display(object interface{}) error {
-	if reflect.TypeOf(object) != reflect.TypeOf(map[string]float64{}) {
-		return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(object))
+	if reflect.TypeOf(object) == reflect.TypeOf(map[string]float64{}) {
+		kvs := []map[string]float64{object.(map[string]float64)}
+
+		return linear.Display(kvs)
 	}
 
-	kvs := object.(map[string]float64)
+	if reflect.TypeOf(object) == reflect.TypeOf([]map[string]float64{}) {
+		kvs := object.([]map[string]float64)
+
+		return linear.Display(kvs)
+	}
 
-	return linear.Display(kvs)
+	return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(object))
 }
diff --git a/display/graph/linear/linear.go b/display/graph/linear/linear.go
index b6e6d07..c632112 100644
--- a/display/graph/linear/linear.go
+++ b/display/graph/linear/linear.go
@@ -19,14 +19,15 @@ package linear
 
 import (
 	"context"
+	"fmt"
+	"math"
 	"strings"
 
-	"github.com/mum4k/termdash/widgetapi"
+	"github.com/mum4k/termdash/linestyle"
 
 	"github.com/mum4k/termdash"
 	"github.com/mum4k/termdash/container"
 	"github.com/mum4k/termdash/container/grid"
-	"github.com/mum4k/termdash/linestyle"
 	"github.com/mum4k/termdash/terminal/termbox"
 	"github.com/mum4k/termdash/terminal/terminalapi"
 	"github.com/mum4k/termdash/widgets/linechart"
@@ -34,20 +35,19 @@ import (
 
 const RootID = "root"
 
-func newWidgets(inputs map[string]float64) (lineChart *linechart.LineChart, err error) {
+func newLineChart(inputs map[string]float64) (lineChart *linechart.LineChart, err error) {
 	index := 0
 
 	xLabels := map[int]string{}
-	var yValues []float64
+	yValues := make([]float64, len(inputs))
+
 	for xLabel, yValue := range inputs {
 		xLabels[index] = xLabel
+		yValues[index] = yValue
 		index++
-		yValues = append(yValues, yValue)
 	}
 
-	if lineChart, err = linechart.New(
-		linechart.YAxisAdaptive(),
-	); err != nil {
+	if lineChart, err = linechart.New(linechart.YAxisAdaptive()); err != nil {
 		return
 	}
 
@@ -56,21 +56,42 @@ func newWidgets(inputs map[string]float64) (lineChart *linechart.LineChart, err
 	return lineChart, err
 }
 
-func gridLayout(lineChart widgetapi.Widget) ([]container.Option, error) {
-	widget := grid.Widget(
-		lineChart,
-		container.Border(linestyle.Light),
-		container.BorderTitleAlignCenter(),
-		container.BorderTitle("Press q to quit"),
-	)
+func layout(lineCharts ...*linechart.LineChart) ([]container.Option, error) {
+	cols := maxSqrt(len(lineCharts))
+
+	rows := make([][]grid.Element, int(math.Ceil(float64(len(lineCharts))/float64(cols))))
+
+	for r := 0; r < len(rows); r++ {
+		var row []grid.Element
+		for c := 0; c < cols && r*cols+c < len(lineCharts); c++ {
+			percentage := int(math.Floor(float64(100) / float64(cols)))
+			if r == len(rows)-1 {
+				percentage = int(math.Floor(float64(100) / float64(len(lineCharts)-r*cols)))
+			}
+			row = append(row, grid.ColWidthPerc(
+				int(math.Min(99, float64(percentage))),
+				grid.Widget(
+					lineCharts[r*cols+c],
+					container.Border(linestyle.Light),
+					container.BorderTitleAlignCenter(),
+					container.BorderTitle(fmt.Sprintf("#%v", r*cols+c)),
+				),
+			))
+		}
+		rows[r] = row
+	}
 
 	builder := grid.New()
-	builder.Add(widget)
+
+	for _, row := range rows {
+		percentage := int(math.Min(99, float64(100/len(rows))))
+		builder.Add(grid.RowHeightPerc(percentage, row...))
+	}
 
 	return builder.Build()
 }
 
-func Display(inputs map[string]float64) error {
+func Display(inputs []map[string]float64) error {
 	t, err := termbox.New()
 	if err != nil {
 		return err
@@ -80,26 +101,31 @@ func Display(inputs map[string]float64) error {
 	c, err := container.New(
 		t,
 		container.ID(RootID),
-		container.PaddingTop(2),
-		container.PaddingRight(2),
-		container.PaddingBottom(2),
-		container.PaddingLeft(2),
 	)
 	if err != nil {
 		return err
 	}
 
-	w, err := newWidgets(inputs)
-	if err != nil {
-		return err
+	var elements []*linechart.LineChart
+
+	for _, input := range inputs {
+		w, e := newLineChart(input)
+		if e != nil {
+			return e
+		}
+		elements = append(elements, w)
 	}
 
-	gridOpts, err := gridLayout(w)
+	gridOpts, err := layout(elements...)
 	if err != nil {
 		return err
 	}
 
-	err = c.Update(RootID, gridOpts...)
+	err = c.Update(RootID, append(
+		gridOpts,
+		container.Border(linestyle.Light),
+		container.BorderTitle("PRESS Q TO QUIT"))...,
+	)
 
 	if err != nil {
 		return err
@@ -116,3 +142,7 @@ func Display(inputs map[string]float64) error {
 
 	return err
 }
+
+func maxSqrt(num int) int {
+	return int(math.Ceil(math.Sqrt(float64(num))))
+}
diff --git a/graphql/client/client.go b/graphql/client/client.go
index a0766ef..a14f2f8 100644
--- a/graphql/client/client.go
+++ b/graphql/client/client.go
@@ -19,13 +19,10 @@ package client
 
 import (
 	"context"
-	"fmt"
-	"time"
 
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
 
-	"github.com/apache/skywalking-cli/graphql/schema"
 	"github.com/apache/skywalking-cli/logger"
 )
 
@@ -37,6 +34,7 @@ func newClient(cliCtx *cli.Context) (client *graphql.Client) {
 	return
 }
 
+// ExecuteQuery executes the `request` and parse to the `response`, returning `error` if there is any.
 func ExecuteQuery(cliCtx *cli.Context, request *graphql.Request, response interface{}) error {
 	client := newClient(cliCtx)
 	ctx := context.Background()
@@ -44,151 +42,9 @@ func ExecuteQuery(cliCtx *cli.Context, request *graphql.Request, response interf
 	return err
 }
 
+// ExecuteQuery executes the `request` and parse to the `response`, panic if there is any `error`.
 func ExecuteQueryOrFail(cliCtx *cli.Context, request *graphql.Request, response interface{}) {
-	client := newClient(cliCtx)
-	ctx := context.Background()
-	if err := client.Run(ctx, request, response); err != nil {
-		logger.Log.Fatalln(err)
-	}
-}
-
-func Services(cliCtx *cli.Context, duration schema.Duration) []schema.Service {
-	var response map[string][]schema.Service
-	request := graphql.NewRequest(`
-		query ($duration: Duration!) {
-			services: getAllServices(duration: $duration) {
-				id name
-			}
-		}
-	`)
-	request.Var("duration", duration)
-
-	ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["services"]
-}
-
-func SearchEndpoints(cliCtx *cli.Context, serviceID, keyword string, limit int) []schema.Endpoint {
-	var response map[string][]schema.Endpoint
-	request := graphql.NewRequest(`
-		query ($keyword: String!, $serviceId: ID!, $limit: Int!) {
-			endpoints: searchEndpoint(keyword: $keyword, serviceId: $serviceId, limit: $limit) {
-				id name
-			}
-		}
-	`)
-	request.Var("serviceId", serviceID)
-	request.Var("keyword", keyword)
-	request.Var("limit", limit)
-
-	ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["endpoints"]
-}
-
-func GetEndpointInfo(cliCtx *cli.Context, endpointID string) schema.Endpoint {
-	var response map[string]schema.Endpoint
-	request := graphql.NewRequest(`
-		query ($endpointId: ID!) {
-			endpoint: getEndpointInfo(endpointId: $endpointId) {
-				id name
-			}
-		}
-	`)
-	request.Var("endpointId", endpointID)
-
-	ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["endpoint"]
-}
-
-func Instances(cliCtx *cli.Context, serviceID string, duration schema.Duration) []schema.ServiceInstance {
-	var response map[string][]schema.ServiceInstance
-	request := graphql.NewRequest(`
-		query ($serviceId: ID!, $duration: Duration!) {
-			instances: getServiceInstances(duration: $duration, serviceId: $serviceId) {
-				id
-				name
-				language
-				instanceUUID
-				attributes {
-					name
-					value
-				}
-			}
-		}
-	`)
-	request.Var("serviceId", serviceID)
-	request.Var("duration", duration)
-
-	ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["instances"]
-}
-
-func SearchService(cliCtx *cli.Context, serviceCode string) (service schema.Service, err error) {
-	var response map[string]schema.Service
-	request := graphql.NewRequest(`
-		query searchService($serviceCode: String!) {
-			service: searchService(serviceCode: $serviceCode) {
-				id name
-			}
-		}
-	`)
-	request.Var("serviceCode", serviceCode)
-
-	ExecuteQueryOrFail(cliCtx, request, &response)
-	service = response["service"]
-	if service.ID == "" {
-		return service, fmt.Errorf("no such service [%s]", serviceCode)
-	}
-	return service, nil
-}
-
-func LinearIntValues(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) map[string]float64 {
-	var response map[string]schema.IntValues
-
-	request := graphql.NewRequest(`
-		query ($metric: MetricCondition!, $duration: Duration!) {
-			metrics: getLinearIntValues(metric: $metric, duration: $duration) {
-				values { value }
-			}
-		}
-	`)
-	request.Var("metric", condition)
-	request.Var("duration", duration)
-
-	ExecuteQueryOrFail(ctx, request, &response)
-
-	values := metricsToMap(duration, response["metrics"].Values)
-
-	return values
-}
-
-func IntValues(ctx *cli.Context, condition schema.BatchMetricConditions, duration schema.Duration) []*schema.KVInt {
-	var response map[string]schema.IntValues
-
-	request := graphql.NewRequest(`
-		query ($metric: BatchMetricConditions!, $duration: Duration!) {
-			metrics: getValues(metric: $metric, duration: $duration) {
-				values { id value }
-			}
-		}
-	`)
-	request.Var("metric", condition)
-	request.Var("duration", duration)
-
-	ExecuteQueryOrFail(ctx, request, &response)
-
-	return response["metrics"].Values
-}
-
-func metricsToMap(duration schema.Duration, kvInts []*schema.KVInt) map[string]float64 {
-	values := map[string]float64{}
-	format := schema.StepFormats[duration.Step]
-	startTime, err := time.Parse(format, duration.Start)
-	if err != nil {
+	if err := ExecuteQuery(cliCtx, request, response); err != nil {
 		logger.Log.Fatalln(err)
 	}
-	step := schema.StepDuration[duration.Step]
-	for idx, value := range kvInts {
-		values[startTime.Add(time.Duration(idx)*step).Format(format)] = float64(value.Value)
-	}
-	return values
 }
diff --git a/graphql/metadata/metadata.go b/graphql/metadata/metadata.go
index 55a1bc7..93b6875 100644
--- a/graphql/metadata/metadata.go
+++ b/graphql/metadata/metadata.go
@@ -18,6 +18,8 @@
 package metadata
 
 import (
+	"fmt"
+
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
 
@@ -25,7 +27,97 @@ import (
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
+func AllServices(cliCtx *cli.Context, duration schema.Duration) []schema.Service {
+	var response map[string][]schema.Service
+	request := graphql.NewRequest(`
+		query ($duration: Duration!) {
+			services: getAllServices(duration: $duration) {
+				id name
+			}
+		}
+	`)
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(cliCtx, request, &response)
+	return response["services"]
+}
+
+func SearchService(cliCtx *cli.Context, serviceCode string) (service schema.Service, err error) {
+	var response map[string]schema.Service
+	request := graphql.NewRequest(`
+		query searchService($serviceCode: String!) {
+			service: searchService(serviceCode: $serviceCode) {
+				id name
+			}
+		}
+	`)
+	request.Var("serviceCode", serviceCode)
+
+	client.ExecuteQueryOrFail(cliCtx, request, &response)
+	service = response["service"]
+	if service.ID == "" {
+		return service, fmt.Errorf("no such service [%s]", serviceCode)
+	}
+	return service, nil
+}
+
+func SearchEndpoints(cliCtx *cli.Context, serviceID, keyword string, limit int) []schema.Endpoint {
+	var response map[string][]schema.Endpoint
+	request := graphql.NewRequest(`
+		query ($keyword: String!, $serviceId: ID!, $limit: Int!) {
+			endpoints: searchEndpoint(keyword: $keyword, serviceId: $serviceId, limit: $limit) {
+				id name
+			}
+		}
+	`)
+	request.Var("serviceId", serviceID)
+	request.Var("keyword", keyword)
+	request.Var("limit", limit)
+
+	client.ExecuteQueryOrFail(cliCtx, request, &response)
+	return response["endpoints"]
+}
+
+func EndpointInfo(cliCtx *cli.Context, endpointID string) schema.Endpoint {
+	var response map[string]schema.Endpoint
+	request := graphql.NewRequest(`
+		query ($endpointId: ID!) {
+			endpoint: getEndpointInfo(endpointId: $endpointId) {
+				id name
+			}
+		}
+	`)
+	request.Var("endpointId", endpointID)
+
+	client.ExecuteQueryOrFail(cliCtx, request, &response)
+	return response["endpoint"]
+}
+
+func Instances(cliCtx *cli.Context, serviceID string, duration schema.Duration) []schema.ServiceInstance {
+	var response map[string][]schema.ServiceInstance
+	request := graphql.NewRequest(`
+		query ($serviceId: ID!, $duration: Duration!) {
+			instances: getServiceInstances(duration: $duration, serviceId: $serviceId) {
+				id
+				name
+				language
+				instanceUUID
+				attributes {
+					name
+					value
+				}
+			}
+		}
+	`)
+	request.Var("serviceId", serviceID)
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(cliCtx, request, &response)
+	return response["instances"]
+}
+
 func ServerTimeInfo(cliCtx *cli.Context) (schema.TimeInfo, error) {
+	var response map[string]schema.TimeInfo
 	request := graphql.NewRequest(`
 		query {
 			timeInfo: getTimeInfo {
@@ -34,7 +126,6 @@ func ServerTimeInfo(cliCtx *cli.Context) (schema.TimeInfo, error) {
 		}
 	`)
 
-	var response map[string]schema.TimeInfo
 	if err := client.ExecuteQuery(cliCtx, request, &response); err != nil {
 		return schema.TimeInfo{}, err
 	}
diff --git a/graphql/metrics/metrics.go b/graphql/metrics/metrics.go
new file mode 100644
index 0000000..c086e51
--- /dev/null
+++ b/graphql/metrics/metrics.go
@@ -0,0 +1,82 @@
+// Licensed to 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. Apache Software Foundation (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 metrics
+
+import (
+	"github.com/machinebox/graphql"
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/graphql/client"
+
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
+
+func IntValues(ctx *cli.Context, condition schema.BatchMetricConditions, duration schema.Duration) schema.IntValues {
+	var response map[string]schema.IntValues
+
+	request := graphql.NewRequest(`
+		query ($metric: BatchMetricConditions!, $duration: Duration!) {
+			metrics: getValues(metric: $metric, duration: $duration) {
+				values { id value }
+			}
+		}
+	`)
+	request.Var("metric", condition)
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["metrics"]
+}
+
+func LinearIntValues(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) schema.IntValues {
+	var response map[string]schema.IntValues
+
+	request := graphql.NewRequest(`
+		query ($metric: MetricCondition!, $duration: Duration!) {
+			metrics: getLinearIntValues(metric: $metric, duration: $duration) {
+				values { value }
+			}
+		}
+	`)
+	request.Var("metric", condition)
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["metrics"]
+}
+
+func MultipleLinearIntValues(ctx *cli.Context, condition schema.MetricCondition, numOfLinear int, duration schema.Duration) []schema.IntValues {
+	request := graphql.NewRequest(`
+		query ($metric: MetricCondition!, $numOfLinear: Int!, $duration: Duration!) {
+			metrics: getMultipleLinearIntValues(metric: $metric, numOfLinear: $numOfLinear, duration: $duration) {
+				values { value }
+			}
+		}
+	`)
+	request.Var("metric", condition)
+	request.Var("numOfLinear", numOfLinear)
+	request.Var("duration", duration)
+
+	var response map[string][]schema.IntValues
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["metrics"]
+}
diff --git a/graphql/metadata/metadata.go b/graphql/utils/adapter.go
similarity index 61%
copy from graphql/metadata/metadata.go
copy to graphql/utils/adapter.go
index 55a1bc7..baec147 100644
--- a/graphql/metadata/metadata.go
+++ b/graphql/utils/adapter.go
@@ -15,28 +15,31 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package metadata
+package utils
 
 import (
-	"github.com/machinebox/graphql"
-	"github.com/urfave/cli"
+	"time"
 
-	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
+	"github.com/apache/skywalking-cli/logger"
 )
 
-func ServerTimeInfo(cliCtx *cli.Context) (schema.TimeInfo, error) {
-	request := graphql.NewRequest(`
-		query {
-			timeInfo: getTimeInfo {
-				timezone, currentTimestamp
-			}
-		}
-	`)
-
-	var response map[string]schema.TimeInfo
-	if err := client.ExecuteQuery(cliCtx, request, &response); err != nil {
-		return schema.TimeInfo{}, err
+type IntValues schema.IntValues
+
+func MetricsToMap(duration schema.Duration, intValues schema.IntValues) map[string]float64 {
+	kvInts := intValues.Values
+	values := map[string]float64{}
+	format := StepFormats[duration.Step]
+	startTime, err := time.Parse(format, duration.Start)
+
+	if err != nil {
+		logger.Log.Fatalln(err)
 	}
-	return response["timeInfo"], nil
+
+	step := StepDuration[duration.Step]
+	for idx, value := range kvInts {
+		values[startTime.Add(time.Duration(idx)*step).Format(format)] = float64(value.Value)
+	}
+
+	return values
 }
diff --git a/graphql/schema/constants.go b/graphql/utils/constants.go
similarity index 64%
rename from graphql/schema/constants.go
rename to graphql/utils/constants.go
index f146338..b4f0f3d 100644
--- a/graphql/schema/constants.go
+++ b/graphql/utils/constants.go
@@ -15,24 +15,28 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package schema
+package utils
 
-import "time"
+import (
+	"time"
+
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
 
 // StepFormats is a mapping from schema.Step to its time format
-var StepFormats = map[Step]string{
-	StepSecond: "2006-01-02 150400",
-	StepMinute: "2006-01-02 1504",
-	StepHour:   "2006-01-02 15",
-	StepDay:    "2006-01-02",
-	StepMonth:  "2006-01",
+var StepFormats = map[schema.Step]string{
+	schema.StepSecond: "2006-01-02 150400",
+	schema.StepMinute: "2006-01-02 1504",
+	schema.StepHour:   "2006-01-02 15",
+	schema.StepDay:    "2006-01-02",
+	schema.StepMonth:  "2006-01",
 }
 
 // StepDuration is a mapping from schema.Step to its time.Duration
-var StepDuration = map[Step]time.Duration{
-	StepSecond: time.Second,
-	StepMinute: time.Minute,
-	StepHour:   time.Hour,
-	StepDay:    time.Hour * 24,
-	StepMonth:  time.Hour * 24 * 30,
+var StepDuration = map[schema.Step]time.Duration{
+	schema.StepSecond: time.Second,
+	schema.StepMinute: time.Minute,
+	schema.StepHour:   time.Hour,
+	schema.StepDay:    time.Hour * 24,
+	schema.StepMonth:  time.Hour * 24 * 30,
 }
diff --git a/swctl/main.go b/swctl/main.go
index 2efdee7..6c39a23 100644
--- a/swctl/main.go
+++ b/swctl/main.go
@@ -21,10 +21,9 @@ import (
 	"io/ioutil"
 	"os"
 
-	"github.com/apache/skywalking-cli/commands/endpoint"
-	linearMetrics "github.com/apache/skywalking-cli/commands/metrics/linear"
-	singleMetrics "github.com/apache/skywalking-cli/commands/metrics/single"
+	"github.com/apache/skywalking-cli/commands/metrics"
 
+	"github.com/apache/skywalking-cli/commands/endpoint"
 	"github.com/apache/skywalking-cli/commands/instance"
 
 	"github.com/sirupsen/logrus"
@@ -81,11 +80,10 @@ func main() {
 	}
 
 	app.Commands = []cli.Command{
-		service.Command,
-		instance.Command,
-		linearMetrics.Command,
-		singleMetrics.Command,
 		endpoint.Command,
+		instance.Command,
+		service.Command,
+		metrics.Command,
 	}
 
 	app.Before = interceptor.BeforeChain([]cli.BeforeFunc{