You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/03/29 03:13:27 UTC

[skywalking-cli] 01/01: Add command `trace`

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

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

commit dfa75386b9be473f14ecddd31af8c61dc415b0f9
Author: kezhenxu94 <ke...@163.com>
AuthorDate: Sun Mar 29 11:12:40 2020 +0800

    Add command `trace`
    
    ### Motivation
    
    Add command `trace` to query trace by trace ID and visualize the traces
    
    ### Modification
    
    - Move `main.go` to `cmd`, as per the recommendation of GO CLI package structure.
    
    - Move GraphQL query to individual `*.graphql` file, for more friendly development, with IDE plugin.
    
    ### Result
    
    - Traces can be query and visualized by `swctl --display graph trace <trace id>`.
    
    - GraphQL queries can be debugged in IDE with plugins.
---
 .gitignore                                         |   3 +
 .graphqlconfig                                     |  11 +
 Makefile                                           |  21 +-
 README.md                                          |  40 ++++
 assets/assets.go                                   |  33 +++
 .../graphqls/aggregation/AllEndpointTopN.graphql   |  27 +++
 .../aggregation/AllServiceInstanceTopN.graphql     |  27 +++
 assets/graphqls/aggregation/EndpointTopN.graphql   |  28 +++
 .../aggregation/ServiceInstanceTopN.graphql        |  28 +++
 assets/graphqls/aggregation/ServiceTopN.graphql    |  27 +++
 assets/graphqls/metadata/AllServices.graphql       |  22 ++
 assets/graphqls/metadata/Instances.graphql         |  30 +++
 assets/graphqls/metadata/SearchEndpoints.graphql   |  22 ++
 assets/graphqls/metadata/SearchService.graphql     |  22 ++
 assets/graphqls/metadata/ServerTimeInfo.graphql    |  22 ++
 assets/graphqls/metrics/IntValues.graphql          |  22 ++
 assets/graphqls/metrics/LinearIntValues.graphql    |  22 ++
 .../metrics/MultipleLinearIntValues.graphql        |  22 ++
 assets/graphqls/metrics/Thermodynamic.graphql      |  22 ++
 assets/graphqls/trace/Trace.graphql                |  50 +++++
 {swctl => cmd}/main.go                             |   3 +
 commands/trace/list.go                             |  29 +++
 display/graph/graph.go => commands/trace/trace.go  |  48 ++---
 display/graph/graph.go                             |  37 +++-
 display/graph/tree/adapter.go                      | 128 ++++++++++++
 display/graph/tree/tree.go                         | 224 +++++++++++++++++++++
 dist/LICENSE                                       |   1 +
 go.mod                                             |   1 +
 go.sum                                             | 142 +++++++++++++
 graphql/aggregation/aggregation.go                 |  69 +------
 graphql/metadata/metadata.go                       |  83 ++------
 graphql/metrics/metrics.go                         |  54 ++---
 display/graph/graph.go => graphql/trace/trace.go   |  35 +---
 util/lang.go                                       |  26 +++
 34 files changed, 1153 insertions(+), 228 deletions(-)

diff --git a/.gitignore b/.gitignore
index e6e7bf9..0e27e55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@
 bin
 coverage.txt
 graphql/schema/schema.go
+.DS_Store
+*-packr.go
+packrd
diff --git a/.graphqlconfig b/.graphqlconfig
new file mode 100644
index 0000000..022db9c
--- /dev/null
+++ b/.graphqlconfig
@@ -0,0 +1,11 @@
+{
+  "name": "SkyWalking GraphQL Schema",
+  "extensions": {
+    "endpoints": {
+      "SkyWalking GraphQL Endpoint": {
+        "url": "http://122.112.182.72:8080/graphql",
+        "introspect": false
+      }
+    }
+  }
+}
diff --git a/Makefile b/Makefile
index 885df84..a1d8e19 100644
--- a/Makefile
+++ b/Makefile
@@ -28,10 +28,10 @@ GO = go
 GO_PATH = $$($(GO) env GOPATH)
 GO_BUILD = $(GO) build
 GO_GET = $(GO) get
-GO_CLEAN = $(GO) clean
 GO_TEST = $(GO) test
 GO_LINT = $(GO_PATH)/bin/golangci-lint
 GO_LICENSER = $(GO_PATH)/bin/go-licenser
+GO_PACKR = $(GO_PATH)/bin/packr2
 GO_BUILD_FLAGS = -v
 GO_BUILD_LDFLAGS = -X main.version=$(VERSION)
 
@@ -41,20 +41,21 @@ ARCH = amd64
 
 SHELL = /bin/bash
 
-all: clean deps codegen lint test license build
+all: clean license deps codegen lint test build
 
 deps:
 	$(GO_GET) -v -t -d ./...
 
-codegen:
+codegen: clean
 	echo 'scalar Long' > query-protocol/schema.graphqls
 	$(GO) run github.com/99designs/gqlgen generate
 	-rm -rf generated.go
+	cd assets && GO111MODULE=on $(GO_PACKR) -v && cd ..
 
 .PHONY: $(PLATFORMS)
 $(PLATFORMS):
 	mkdir -p $(OUT_DIR)
-	GOOS=$(os) GOARCH=$(ARCH) $(GO_BUILD) $(GO_BUILD_FLAGS) -ldflags "$(GO_BUILD_LDFLAGS)" -o $(OUT_DIR)/$(BINARY)-$(VERSION)-$(os)-$(ARCH) swctl/main.go
+	GOOS=$(os) GOARCH=$(ARCH) $(GO_BUILD) $(GO_BUILD_FLAGS) -ldflags "$(GO_BUILD_LDFLAGS)" -o $(OUT_DIR)/$(BINARY)-$(VERSION)-$(os)-$(ARCH) cmd/main.go
 
 .PHONY: lint
 lint: codegen
@@ -71,7 +72,7 @@ build: deps windows linux darwin
 .PHONY: license
 license: clean
 	$(GO_LICENSER) -version || GO111MODULE=off $(GO_GET) -u github.com/elastic/go-licenser
-	$(GO_LICENSER) -exclude graphql/schema/ -d -licensor='Apache Software Foundation (ASF)' .
+	$(GO_LICENSER) -d -licensor='Apache Software Foundation (ASF)' .
 
 .PHONY: verify
 verify: clean lint test license
@@ -86,15 +87,16 @@ coverage: test
 	bash <(curl -s https://codecov.io/bash) -t a5af28a3-92a2-4b35-9a77-54ad99b1ae00
 
 .PHONY: clean
-clean: codegen
-	$(GO_CLEAN) ./...
+clean:
 	-rm -rf bin
 	-rm -rf coverage.txt
+	-rm -rf query-protocol/schema.graphqls
+	-rm -rf graphql/schema/schema.go
 	-rm -rf *.tgz
 	-rm -rf *.tgz
 	-rm -rf *.asc
 	-rm -rf *.sha512
-	-rm -rf query-protocol/schema.graphqls
+	cd assets && $(GO_PACKR) clean
 
 release-src: clean
 	-tar -zcvf $(RELEASE_SRC).tgz \
@@ -105,6 +107,9 @@ release-src: clean
 	--exclude .github \
 	--exclude $(RELEASE_SRC).tgz \
 	--exclude graphql/schema/schema.go \
+	--exclude query-protocol/schema.graphqls \
+	--exclude assets/packrd \
+	--exclude assets/*-packr.go \
 	.
 
 release-bin: build
diff --git a/README.md b/README.md
index f5e4268..52951a9 100644
--- a/README.md
+++ b/README.md
@@ -231,6 +231,36 @@ Ascii Graph, like coloring in terminal, so please use `json`  or `yaml` instead.
 
 </details>
 
+<details>
+
+<summary>instance search [--start=start-time] [--end=end-time] [--regex=instance-name-regex] [--service-id=service-id] [--service-name=service-name]</summary>
+
+`instance search` filter the instance in the time range of `[start, end]` and given --regex --service-id or --service-name.
+
+| option | description | default |
+| :--- | :--- | :--- |
+| `--regex` | Query regex of instance name|  |
+| `--service-id` | Query by service id (priority over `--service-name`)|  |
+| `--service-name` | Query by service name if `service-id` is absent |  |
+| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
+
+</details>
+
+### `trace`
+
+<details>
+
+<summary>trace [trace id]</summary>
+
+`trace` displays the spans of a given trace.
+
+| argument | description | default |
+| :--- | :--- | :--- |
+| `trace id` | the trace id whose spans are to displayed |  |
+
+</details>
+
 # Use Cases
 
 <details>
@@ -477,6 +507,16 @@ $ ./bin/swctl --display=graph metrics thermodynamic --name all_heatmap
 
 <details>
 
+<summary>Display the spans of a trace</summary>
+
+```shell
+$ ./bin/swctl --display graph trace 1585375544413.464998031.46647
+```
+
+</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/assets/assets.go b/assets/assets.go
new file mode 100644
index 0000000..d5964f9
--- /dev/null
+++ b/assets/assets.go
@@ -0,0 +1,33 @@
+// 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 assets
+
+import (
+	"github.com/gobuffalo/packr/v2"
+
+	"github.com/apache/skywalking-cli/logger"
+)
+
+// Read reads all content from a file under assets, which is packed in to the binary
+func Read(filename string) string {
+	content, err := packr.New("assets", ".").FindString(filename)
+	if err != nil {
+		logger.Log.Fatalln("failed to read asset: ", filename, err)
+	}
+	return content
+}
diff --git a/assets/graphqls/aggregation/AllEndpointTopN.graphql b/assets/graphqls/aggregation/AllEndpointTopN.graphql
new file mode 100644
index 0000000..a054b10
--- /dev/null
+++ b/assets/graphqls/aggregation/AllEndpointTopN.graphql
@@ -0,0 +1,27 @@
+# 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.
+
+query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+    result: getAllEndpointTopN(
+        duration: $duration,
+        name: $name,
+        topN: $topN,
+        order: $order
+    ) {
+        id name value
+    }
+}
diff --git a/assets/graphqls/aggregation/AllServiceInstanceTopN.graphql b/assets/graphqls/aggregation/AllServiceInstanceTopN.graphql
new file mode 100644
index 0000000..0268c60
--- /dev/null
+++ b/assets/graphqls/aggregation/AllServiceInstanceTopN.graphql
@@ -0,0 +1,27 @@
+# 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.
+
+query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+    result: getAllServiceInstanceTopN(
+        duration: $duration,
+        name: $name,
+        topN: $topN,
+        order: $order
+    ) {
+        id name value
+    }
+}
diff --git a/assets/graphqls/aggregation/EndpointTopN.graphql b/assets/graphqls/aggregation/EndpointTopN.graphql
new file mode 100644
index 0000000..b6e05f6
--- /dev/null
+++ b/assets/graphqls/aggregation/EndpointTopN.graphql
@@ -0,0 +1,28 @@
+# 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.
+
+query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+    result: getEndpointTopN(
+        serviceId: $serviceId,
+        duration: $duration,
+        name: $name,
+        topN: $topN,
+        order: $order
+    ) {
+        id name value
+    }
+}
diff --git a/assets/graphqls/aggregation/ServiceInstanceTopN.graphql b/assets/graphqls/aggregation/ServiceInstanceTopN.graphql
new file mode 100644
index 0000000..f643170
--- /dev/null
+++ b/assets/graphqls/aggregation/ServiceInstanceTopN.graphql
@@ -0,0 +1,28 @@
+# 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.
+
+query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+    result: getServiceInstanceTopN(
+        serviceId: $serviceId,
+        duration: $duration,
+        name: $name,
+        topN: $topN,
+        order: $order
+    ) {
+        id name value
+    }
+}
diff --git a/assets/graphqls/aggregation/ServiceTopN.graphql b/assets/graphqls/aggregation/ServiceTopN.graphql
new file mode 100644
index 0000000..754e827
--- /dev/null
+++ b/assets/graphqls/aggregation/ServiceTopN.graphql
@@ -0,0 +1,27 @@
+# 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.
+
+query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+    result: getServiceTopN(
+        duration: $duration,
+        name: $name,
+        topN: $topN,
+        order: $order
+    ) {
+        id name value
+    }
+}
diff --git a/assets/graphqls/metadata/AllServices.graphql b/assets/graphqls/metadata/AllServices.graphql
new file mode 100644
index 0000000..d3a3f86
--- /dev/null
+++ b/assets/graphqls/metadata/AllServices.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($duration: Duration!) {
+    result: getAllServices(duration: $duration) {
+        id name
+    }
+}
diff --git a/assets/graphqls/metadata/Instances.graphql b/assets/graphqls/metadata/Instances.graphql
new file mode 100644
index 0000000..4bb4cbc
--- /dev/null
+++ b/assets/graphqls/metadata/Instances.graphql
@@ -0,0 +1,30 @@
+# 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.
+
+
+query ($serviceId: ID!, $duration: Duration!) {
+    result: getServiceInstances(duration: $duration, serviceId: $serviceId) {
+        id
+        name
+        language
+        instanceUUID
+        attributes {
+            name
+            value
+        }
+    }
+}
diff --git a/assets/graphqls/metadata/SearchEndpoints.graphql b/assets/graphqls/metadata/SearchEndpoints.graphql
new file mode 100644
index 0000000..2c21f47
--- /dev/null
+++ b/assets/graphqls/metadata/SearchEndpoints.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($keyword: String!, $serviceId: ID!, $limit: Int!) {
+    result: searchEndpoint(keyword: $keyword, serviceId: $serviceId, limit: $limit) {
+        id name
+    }
+}
diff --git a/assets/graphqls/metadata/SearchService.graphql b/assets/graphqls/metadata/SearchService.graphql
new file mode 100644
index 0000000..525446e
--- /dev/null
+++ b/assets/graphqls/metadata/SearchService.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query searchService($serviceCode: String!) {
+    result: searchService(serviceCode: $serviceCode) {
+        id name
+    }
+}
diff --git a/assets/graphqls/metadata/ServerTimeInfo.graphql b/assets/graphqls/metadata/ServerTimeInfo.graphql
new file mode 100644
index 0000000..d00d8a9
--- /dev/null
+++ b/assets/graphqls/metadata/ServerTimeInfo.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query {
+    result: getTimeInfo {
+        timezone, currentTimestamp
+    }
+}
diff --git a/assets/graphqls/metrics/IntValues.graphql b/assets/graphqls/metrics/IntValues.graphql
new file mode 100644
index 0000000..1138172
--- /dev/null
+++ b/assets/graphqls/metrics/IntValues.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($metric: BatchMetricConditions!, $duration: Duration!) {
+    result: getValues(metric: $metric, duration: $duration) {
+        values { id value }
+    }
+}
diff --git a/assets/graphqls/metrics/LinearIntValues.graphql b/assets/graphqls/metrics/LinearIntValues.graphql
new file mode 100644
index 0000000..6249db2
--- /dev/null
+++ b/assets/graphqls/metrics/LinearIntValues.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($metric: MetricCondition!, $duration: Duration!) {
+    result: getLinearIntValues(metric: $metric, duration: $duration) {
+        values { value }
+    }
+}
diff --git a/assets/graphqls/metrics/MultipleLinearIntValues.graphql b/assets/graphqls/metrics/MultipleLinearIntValues.graphql
new file mode 100644
index 0000000..2450068
--- /dev/null
+++ b/assets/graphqls/metrics/MultipleLinearIntValues.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($metric: MetricCondition!, $numOfLinear: Int!, $duration: Duration!) {
+    result: getMultipleLinearIntValues(metric: $metric, numOfLinear: $numOfLinear, duration: $duration) {
+        values { value }
+    }
+}
diff --git a/assets/graphqls/metrics/Thermodynamic.graphql b/assets/graphqls/metrics/Thermodynamic.graphql
new file mode 100644
index 0000000..9ff50bd
--- /dev/null
+++ b/assets/graphqls/metrics/Thermodynamic.graphql
@@ -0,0 +1,22 @@
+# 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.
+
+query ($metric: MetricCondition!, $duration: Duration!) {
+    result: getThermodynamic(metric: $metric, duration: $duration) {
+        nodes axisYStep
+    }
+}
diff --git a/assets/graphqls/trace/Trace.graphql b/assets/graphqls/trace/Trace.graphql
new file mode 100644
index 0000000..233fc9f
--- /dev/null
+++ b/assets/graphqls/trace/Trace.graphql
@@ -0,0 +1,50 @@
+# 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.
+
+query ($traceId: ID!) {
+    result: queryTrace(traceId: $traceId) {
+        spans {
+            traceId
+            segmentId
+            spanId
+            parentSpanId
+            refs {
+                traceId
+                parentSegmentId
+                parentSpanId
+                type
+            }
+            serviceCode
+            startTime
+            endTime
+            endpointName
+            type
+            peer
+            component
+            isError
+            layer
+            tags {
+                key value
+            }
+            logs {
+                time data {
+                    key value
+                }
+            }
+        }
+    }
+}
diff --git a/swctl/main.go b/cmd/main.go
similarity index 98%
rename from swctl/main.go
rename to cmd/main.go
index 6c39a23..6577b67 100644
--- a/swctl/main.go
+++ b/cmd/main.go
@@ -21,6 +21,8 @@ import (
 	"io/ioutil"
 	"os"
 
+	"github.com/apache/skywalking-cli/commands/trace"
+
 	"github.com/apache/skywalking-cli/commands/metrics"
 
 	"github.com/apache/skywalking-cli/commands/endpoint"
@@ -84,6 +86,7 @@ func main() {
 		instance.Command,
 		service.Command,
 		metrics.Command,
+		trace.Command,
 	}
 
 	app.Before = interceptor.BeforeChain([]cli.BeforeFunc{
diff --git a/commands/trace/list.go b/commands/trace/list.go
new file mode 100644
index 0000000..f23845e
--- /dev/null
+++ b/commands/trace/list.go
@@ -0,0 +1,29 @@
+// 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 trace
+
+import "github.com/urfave/cli"
+
+var ListCommand = cli.Command{
+	Name:      "list",
+	ShortName: "ls",
+	Usage:     "query traces",
+	Action: func(ctx *cli.Context) error {
+		return nil
+	},
+}
diff --git a/display/graph/graph.go b/commands/trace/trace.go
similarity index 50%
copy from display/graph/graph.go
copy to commands/trace/trace.go
index e8fc072..a6dd408 100644
--- a/display/graph/graph.go
+++ b/commands/trace/trace.go
@@ -15,37 +15,33 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package graph
+package trace
 
 import (
 	"fmt"
-	"reflect"
 
-	"github.com/apache/skywalking-cli/display/graph/heatmap"
-	"github.com/apache/skywalking-cli/graphql/schema"
+	"github.com/urfave/cli"
 
-	d "github.com/apache/skywalking-cli/display/displayable"
-	"github.com/apache/skywalking-cli/display/graph/linear"
+	"github.com/apache/skywalking-cli/display"
+	"github.com/apache/skywalking-cli/display/displayable"
+	"github.com/apache/skywalking-cli/graphql/trace"
 )
 
-func Display(displayable *d.Displayable) error {
-	data := displayable.Data
-
-	if reflect.TypeOf(data) == reflect.TypeOf(schema.Thermodynamic{}) {
-		return heatmap.Display(displayable)
-	}
-
-	if reflect.TypeOf(data) == reflect.TypeOf(map[string]float64{}) {
-		kvs := []map[string]float64{data.(map[string]float64)}
-
-		return linear.Display(kvs)
-	}
-
-	if reflect.TypeOf(data) == reflect.TypeOf([]map[string]float64{}) {
-		kvs := data.([]map[string]float64)
-
-		return linear.Display(kvs)
-	}
-
-	return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
+var Command = cli.Command{
+	Name:      "trace",
+	ShortName: "t",
+	Usage:     "trace related sub-command",
+	ArgsUsage: "trace id",
+	Action: func(ctx *cli.Context) error {
+		if ctx.NArg() == 0 {
+			return fmt.Errorf("command trace without sub command requires 1 trace id as argument")
+		}
+
+		trace := trace.Trace(ctx, ctx.Args().First())
+
+		return display.Display(ctx, &displayable.Displayable{Data: trace})
+	},
+	Subcommands: cli.Commands{
+		ListCommand,
+	},
 }
diff --git a/display/graph/graph.go b/display/graph/graph.go
index e8fc072..f951b51 100644
--- a/display/graph/graph.go
+++ b/display/graph/graph.go
@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/apache/skywalking-cli/display/graph/tree"
+
 	"github.com/apache/skywalking-cli/display/graph/heatmap"
 	"github.com/apache/skywalking-cli/graphql/schema"
 
@@ -28,24 +30,37 @@ import (
 	"github.com/apache/skywalking-cli/display/graph/linear"
 )
 
+type (
+	Thermodynamic      = schema.Thermodynamic
+	LinearMetrics      = map[string]float64
+	MultiLinearMetrics = []LinearMetrics
+	Trace              = schema.Trace
+)
+
+var (
+	ThermodynamicType      = reflect.TypeOf(Thermodynamic{})
+	LinearMetricsType      = reflect.TypeOf(LinearMetrics{})
+	MultiLinearMetricsType = reflect.TypeOf(MultiLinearMetrics{})
+	TraceType              = reflect.TypeOf(Trace{})
+)
+
 func Display(displayable *d.Displayable) error {
 	data := displayable.Data
 
-	if reflect.TypeOf(data) == reflect.TypeOf(schema.Thermodynamic{}) {
+	switch reflect.TypeOf(data) {
+	case ThermodynamicType:
 		return heatmap.Display(displayable)
-	}
 
-	if reflect.TypeOf(data) == reflect.TypeOf(map[string]float64{}) {
-		kvs := []map[string]float64{data.(map[string]float64)}
+	case LinearMetricsType:
+		return linear.Display([]LinearMetrics{data.(LinearMetrics)})
 
-		return linear.Display(kvs)
-	}
+	case MultiLinearMetricsType:
+		return linear.Display(data.(MultiLinearMetrics))
 
-	if reflect.TypeOf(data) == reflect.TypeOf([]map[string]float64{}) {
-		kvs := data.([]map[string]float64)
+	case TraceType:
+		return tree.Display(tree.Adapt(data.(Trace)))
 
-		return linear.Display(kvs)
+	default:
+		return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
 	}
-
-	return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
 }
diff --git a/display/graph/tree/adapter.go b/display/graph/tree/adapter.go
new file mode 100644
index 0000000..9c33c32
--- /dev/null
+++ b/display/graph/tree/adapter.go
@@ -0,0 +1,128 @@
+// 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 tree
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/apache/skywalking-cli/graphql/schema"
+	"github.com/apache/skywalking-cli/util"
+)
+
+func Adapt(trace schema.Trace) []*Node {
+	all := make(map[string]*Node)
+
+	for _, span := range trace.Spans {
+		all[id(span)] = node(span)
+	}
+
+	seen := make(map[string]bool)
+
+	var roots []*Node
+
+	for _, span := range trace.Spans {
+		if isRoot(span) {
+			roots = append(roots, all[id(span)])
+			seen[id(span)] = true
+		}
+	}
+
+	for len(seen) < len(trace.Spans) {
+		for _, span := range trace.Spans {
+			if seen[id(span)] {
+				continue
+			}
+
+			if all[pid(span)] != nil {
+				all[pid(span)].Children = append(all[pid(span)].Children, all[id(span)])
+				seen[id(span)] = true
+			}
+
+			for _, ref := range span.Refs {
+				if all[id0(ref)] != nil {
+					all[id0(ref)].Children = append(all[id0(ref)].Children, all[id(span)])
+					seen[id(span)] = true
+				}
+			}
+		}
+	}
+
+	return roots
+}
+
+func isRoot(span *schema.Span) bool {
+	return span.SpanID == 0 && span.ParentSpanID == -1 && len(span.Refs) == 0
+}
+
+func id(span *schema.Span) string {
+	return fmt.Sprintf("%s:%s:%d", span.TraceID, span.SegmentID, span.SpanID)
+}
+
+func pid(span *schema.Span) string {
+	return fmt.Sprintf("%s:%s:%d", span.TraceID, span.SegmentID, span.ParentSpanID)
+}
+
+func id0(ref *schema.Ref) string {
+	return fmt.Sprintf("%s:%s:%d", ref.TraceID, ref.ParentSegmentID, ref.ParentSpanID)
+}
+
+func node(span *schema.Span) *Node {
+	return &Node{
+		Children: []*Node{},
+		Value:    util.Stringify{Str: value(span)},
+		Detail:   detail(span),
+	}
+}
+
+func value(span *schema.Span) string {
+	if *span.IsError {
+		return fmt.Sprintf(
+			"[|%s| %s [%s/%s]](mod:bold,fg:white,bg:red)",
+			span.Type, *span.EndpointName, *span.Component, *span.Layer,
+		)
+	}
+
+	return fmt.Sprintf("[|%s|](fg:bold,fg:green) %s [[%s/%s]](mod:bold,fg:green)",
+		span.Type, *span.EndpointName, *span.Component, *span.Layer,
+	)
+}
+
+func detail(span *schema.Span) string {
+	var lines []string
+
+	lines = append(lines,
+		fmt.Sprintf("[Endpoint    :](mod:bold,fg:red) %s", *span.EndpointName),
+		fmt.Sprintf("[Span Type   :](mod:bold,fg:red) %s", span.Type),
+		fmt.Sprintf("[Component   :](mod:bold,fg:red) %s", *span.Component),
+		fmt.Sprintf("[Peer        :](mod:bold,fg:red) %s", *span.Peer),
+		fmt.Sprintf("[Error       :](mod:bold,fg:red) %t", *span.IsError),
+	)
+
+	for _, tag := range span.Tags {
+		lines = append(lines, fmt.Sprintf("[%-12s:](mod:bold,fg:red) %s", tag.Key, *tag.Value))
+	}
+
+	for _, log := range span.Logs {
+		for _, datum := range log.Data {
+			lines = append(lines, fmt.Sprintf("%-12s: %s", datum.Key, *datum.Value))
+		}
+	}
+
+	return strings.Join(lines, "\n")
+}
diff --git a/display/graph/tree/tree.go b/display/graph/tree/tree.go
new file mode 100644
index 0000000..5df89ef
--- /dev/null
+++ b/display/graph/tree/tree.go
@@ -0,0 +1,224 @@
+// 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 tree
+
+import (
+	"fmt"
+
+	ui "github.com/gizak/termui/v3"
+	"github.com/gizak/termui/v3/widgets"
+
+	"github.com/apache/skywalking-cli/logger"
+)
+
+type Node struct {
+	Children []*Node
+	Detail   string
+	Value    fmt.Stringer
+}
+
+var extra = make(map[*widgets.TreeNode]*Node)
+
+func Display(roots []*Node) error {
+	if err := ui.Init(); err != nil {
+		logger.Log.Fatalf("failed to initialize termui: %v", err)
+	}
+	defer ui.Close()
+
+	nodes := make([]*widgets.TreeNode, len(roots))
+	for i := range nodes {
+		nodes[i] = &widgets.TreeNode{}
+	}
+
+	for i, root := range roots {
+		adapt(root, nodes[i])
+	}
+
+	tree := widgets.NewTree()
+	tree.TextStyle = ui.Style{
+		Fg:       ui.ColorWhite,
+		Bg:       ui.ColorClear,
+		Modifier: 0,
+	}
+	tree.SelectedRowStyle = ui.Style{
+		Fg:       ui.ColorBlack,
+		Bg:       ui.ColorWhite,
+		Modifier: ui.ModifierBold,
+	}
+	tree.WrapText = false
+	tree.SetNodes(nodes)
+	tree.Title = " Press ? to show help "
+	tree.TitleStyle.Modifier = ui.ModifierBold
+	tree.TitleStyle.Fg = ui.ColorRed
+
+	x, y := ui.TerminalDimensions()
+
+	tree.SetRect(0, 0, x, y)
+
+	detail := widgets.NewParagraph()
+	detail.Title = " Detail "
+	detail.WrapText = false
+	detail.SetRect(x, 0, x, y)
+
+	help := widgets.NewParagraph()
+	help.WrapText = false
+	help.SetRect(x, 0, x, y)
+	help.Title = " Keymap "
+	help.Text = `
+		[?          ](fg:red,mod:bold) Toggle this help
+		[k          ](fg:red,mod:bold) Scroll Up
+		[<Up>       ](fg:red,mod:bold) Scroll Up
+		[j          ](fg:red,mod:bold) Scroll Down
+		[<Down>     ](fg:red,mod:bold) Scroll Down
+		[<Ctr-b>    ](fg:red,mod:bold) Scroll Page Up
+		[<Ctr-u>    ](fg:red,mod:bold) Scroll Half Page Up
+		[<Ctr-f>    ](fg:red,mod:bold) Scroll Page Down
+		[<Ctr-d>    ](fg:red,mod:bold) Scroll Half Page Down
+		[<Home>     ](fg:red,mod:bold) Scroll to Top
+		[gg         ](fg:red,mod:bold) Scroll to Top
+		[<Enter>    ](fg:red,mod:bold) Show Trace Detail
+		[<Space>    ](fg:red,mod:bold) Show Trace Detail
+		[o          ](fg:red,mod:bold) Toggle Expand
+		[G          ](fg:red,mod:bold) Scroll to Bottom
+		[<End>      ](fg:red,mod:bold) Scroll to Bottom
+		[E          ](fg:red,mod:bold) Expand All
+		[C          ](fg:red,mod:bold) Collapse All
+		[q          ](fg:red,mod:bold) Quit
+		[<Ctr-c>    ](fg:red,mod:bold) Quit
+	`
+
+	ui.Render(tree, detail, help)
+
+	listenKeyboard(tree, detail, help)
+
+	return nil
+}
+
+func adapt(n1 *Node, n2 *widgets.TreeNode) {
+	if n1 == nil || n2 == nil {
+		return
+	}
+
+	n2.Expanded = true
+	n2.Value = n1.Value
+	n2.Nodes = []*widgets.TreeNode{}
+	extra[n2] = n1
+
+	for _, child := range n1.Children {
+		node := &widgets.TreeNode{}
+		n2.Nodes = append(n2.Nodes, node)
+		adapt(child, node)
+	}
+}
+
+func actions(key string, tree *widgets.Tree) func() {
+	// mostly vim style
+	actions := map[string]func(){
+		"k":      tree.ScrollUp,
+		"<Up>":   tree.ScrollUp,
+		"j":      tree.ScrollDown,
+		"<Down>": tree.ScrollDown,
+		"<C-b>":  tree.ScrollPageUp,
+		"<C-u>":  tree.ScrollHalfPageUp,
+		"<C-f>":  tree.ScrollPageDown,
+		"<C-d>":  tree.ScrollHalfPageDown,
+		"<Home>": tree.ScrollTop,
+		"o":      tree.ToggleExpand,
+		"G":      tree.ScrollBottom,
+		"<End>":  tree.ScrollBottom,
+		"E":      tree.ExpandAll,
+		"C":      tree.CollapseAll,
+		"<Resize>": func() {
+			x, y := ui.TerminalDimensions()
+			tree.SetRect(0, 0, x, y)
+		},
+	}
+
+	return actions[key]
+}
+
+func listenKeyboard(tree *widgets.Tree, detail, help *widgets.Paragraph) {
+	var previousKey string
+	var previousSelected *Node
+
+	visibilities := make(map[interface{}]bool)
+
+	uiEvents := ui.PollEvents()
+
+	for {
+		e := <-uiEvents
+
+		switch e.ID {
+		case "q", "<C-c>":
+			return
+		case "g":
+			if previousKey == "g" {
+				tree.ScrollTop()
+			}
+		case "<Enter>", "<Space>":
+			selected := extra[tree.SelectedNode()]
+			detail.Text = selected.Detail
+
+			selectionChanged := previousSelected != selected
+			visibilities[detail] = selectionChanged || !visibilities[detail]
+
+			previousSelected = selected
+		case "?":
+			visibilities[help] = !visibilities[help]
+		default:
+			if action := actions(e.ID, tree); action != nil {
+				action()
+			}
+		}
+
+		if previousKey == "g" {
+			previousKey = ""
+		} else {
+			previousKey = e.ID
+		}
+
+		redraw(visibilities, tree, detail, help)
+	}
+}
+
+func redraw(shouldShow map[interface{}]bool, tree *widgets.Tree, detail, help *widgets.Paragraph) {
+	x, y := ui.TerminalDimensions()
+
+	shouldDisplaySideBar := shouldShow[detail] || shouldShow[help]
+	if shouldDisplaySideBar {
+		tree.SetRect(0, 0, x*2/3, y)
+	} else {
+		tree.SetRect(0, 0, x, y)
+	}
+
+	if shouldShow[detail] && shouldShow[help] {
+		detail.SetRect(x*2/3, 0, x, y/2)
+		help.SetRect(x*2/3, y/2+1, x, y)
+	} else if shouldShow[help] {
+		detail.SetRect(0, 0, 0, 0)
+		help.SetRect(x*2/3, 0, x, y)
+	} else if shouldShow[detail] {
+		detail.SetRect(x*2/3, 0, x, y)
+		help.SetRect(0, 0, 0, 0)
+	} else {
+		help.SetRect(0, 0, 0, 0)
+		detail.SetRect(0, 0, 0, 0)
+	}
+
+	ui.Render(tree, detail, help)
+}
diff --git a/dist/LICENSE b/dist/LICENSE
index 4dfc4c4..2cbc4ea 100644
--- a/dist/LICENSE
+++ b/dist/LICENSE
@@ -224,6 +224,7 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
 	nsf (termbox-go) 0.0.0-20190817171036-93860e161317: https://github.com/nsf/termbox-go MIT
 	gizak (termui) v3: https://github.com/gizak/termui MIT
 	mattn (go-runewidth) v3: https://github.com/mattn/go-runewidth MIT
+	gobuffalo (packr) v2: https://github.com/gobuffalo/packr MIT
 
 ========================================================================
 BSD licenses
diff --git a/go.mod b/go.mod
index 7586d33..b979715 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.13
 require (
 	github.com/99designs/gqlgen v0.11.1 // indirect
 	github.com/gizak/termui/v3 v3.1.0
+	github.com/gobuffalo/packr/v2 v2.8.0
 	github.com/machinebox/graphql v0.2.2
 	github.com/mattn/go-runewidth v0.0.4
 	github.com/mum4k/termdash v0.10.0
diff --git a/go.sum b/go.sum
index 932479d..019c53d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,30 +1,85 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/99designs/gqlgen v0.11.1 h1:QoSL8/AAJ2T3UOeQbdnBR32JcG4pO08+P/g5jdbFkUg=
 github.com/99designs/gqlgen v0.11.1/go.mod h1:vjFOyBZ7NwDl+GdSD4PFn7BQn5Fy7ohJwXn7Vk8zz+c=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
 github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
 github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gizak/termui v3.1.0+incompatible h1:N3CFm+j087lanTxPpHOmQs0uS3s5I9TxoAFy6DqPqv8=
 github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
 github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
 github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
+github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
+github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
+github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
+github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY=
+github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
 github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karrick/godirwalk v1.15.3 h1:0a2pXOgtB16CqIqXTiT7+K9L73f74n/aNQUnH6Ortew=
+github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -33,6 +88,13 @@ github.com/lytics/logrus v0.0.0-20170528191427-4389a17ed024 h1:QaKVrqyQRNPbdBNCp
 github.com/lytics/logrus v0.0.0-20170528191427-4389a17ed024/go.mod h1:SkQviJ2s7rFyzyuxdVp6osZceHOabU91ZhKsEXF0RWg=
 github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
 github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
+github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
+github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
+github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
+github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -40,23 +102,42 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mum4k/termdash v0.10.0 h1:uqM6ePiMf+smecb1tJJeON36o1hREeCfOmLFG0iz4a0=
 github.com/mum4k/termdash v0.10.0/go.mod h1:l3tO+lJi9LZqXRq7cu7h5/8rDIK3AzelSuq2v/KncxI=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
 github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 h1:hhGN4SFXgXo61Q4Sjj/X9sBjyeSa2kdpaOzCO+8EVQw=
 github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o=
 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -65,26 +146,66 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
 github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
 github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
+golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -93,16 +214,37 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
 golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE=
+golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
diff --git a/graphql/aggregation/aggregation.go b/graphql/aggregation/aggregation.go
index fc11392..6effe5e 100644
--- a/graphql/aggregation/aggregation.go
+++ b/graphql/aggregation/aggregation.go
@@ -21,6 +21,8 @@ import (
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/assets"
+
 	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
@@ -28,18 +30,7 @@ import (
 func ServiceTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
 	var response map[string][]schema.TopNEntity
 
-	request := graphql.NewRequest(`
-		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
-			result: getServiceTopN(
-				duration: $duration,
-				name: $name,
-				topN: $topN,
-				order: $order
-			) {
-				id name value
-			}
-		}
-	`)
+	request := graphql.NewRequest(assets.Read("graphqls/aggregation/ServiceTopN.graphql"))
 	request.Var("name", name)
 	request.Var("topN", topN)
 	request.Var("duration", duration)
@@ -53,18 +44,7 @@ func ServiceTopN(ctx *cli.Context, name string, topN int, duration schema.Durati
 func AllServiceInstanceTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
 	var response map[string][]schema.TopNEntity
 
-	request := graphql.NewRequest(`
-		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
-			result: getAllServiceInstanceTopN(
-				duration: $duration,
-				name: $name,
-				topN: $topN,
-				order: $order
-			) {
-				id name value
-			}
-		}
-	`)
+	request := graphql.NewRequest(assets.Read("graphqls/aggregation/AllServiceInstanceTopN.graphql"))
 	request.Var("name", name)
 	request.Var("topN", topN)
 	request.Var("duration", duration)
@@ -78,19 +58,7 @@ func AllServiceInstanceTopN(ctx *cli.Context, name string, topN int, duration sc
 func ServiceInstanceTopN(ctx *cli.Context, serviceID, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
 	var response map[string][]schema.TopNEntity
 
-	request := graphql.NewRequest(`
-		query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
-			result: getServiceInstanceTopN(
-				serviceId: $serviceId,
-				duration: $duration,
-				name: $name,
-				topN: $topN,
-				order: $order
-			) {
-				id name value
-			}
-		}
-	`)
+	request := graphql.NewRequest(assets.Read("graphqls/aggregation/ServiceInstanceTopN.graphql"))
 	request.Var("serviceId", serviceID)
 	request.Var("name", name)
 	request.Var("topN", topN)
@@ -105,18 +73,7 @@ func ServiceInstanceTopN(ctx *cli.Context, serviceID, name string, topN int, dur
 func AllEndpointTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
 	var response map[string][]schema.TopNEntity
 
-	request := graphql.NewRequest(`
-		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
-			result: getAllEndpointTopN(
-				duration: $duration,
-				name: $name,
-				topN: $topN,
-				order: $order
-			) {
-				id name value
-			}
-		}
-	`)
+	request := graphql.NewRequest(assets.Read("graphqls/aggregation/AllEndpointTopN.graphql"))
 	request.Var("name", name)
 	request.Var("topN", topN)
 	request.Var("duration", duration)
@@ -130,19 +87,7 @@ func AllEndpointTopN(ctx *cli.Context, name string, topN int, duration schema.Du
 func EndpointTopN(ctx *cli.Context, serviceID, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
 	var response map[string][]schema.TopNEntity
 
-	request := graphql.NewRequest(`
-		query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
-			result: getEndpointTopN(
-				serviceId: $serviceId,
-				duration: $duration,
-				name: $name,
-				topN: $topN,
-				order: $order
-			) {
-				id name value
-			}
-		}
-	`)
+	request := graphql.NewRequest(assets.Read("graphqls/aggregation/EndpointTopN.graphql"))
 	request.Var("serviceId", serviceID)
 	request.Var("name", name)
 	request.Var("topN", topN)
diff --git a/graphql/metadata/metadata.go b/graphql/metadata/metadata.go
index 93b6875..49e88be 100644
--- a/graphql/metadata/metadata.go
+++ b/graphql/metadata/metadata.go
@@ -20,6 +20,8 @@ package metadata
 import (
 	"fmt"
 
+	"github.com/apache/skywalking-cli/assets"
+
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
 
@@ -29,105 +31,64 @@ import (
 
 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 := graphql.NewRequest(assets.Read("graphqls/metadata/AllServices.graphql"))
 	request.Var("duration", duration)
 
 	client.ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["services"]
+	return response["result"]
 }
 
 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 := graphql.NewRequest(assets.Read("graphqls/metadata/SearchService.graphql"))
 	request.Var("serviceCode", serviceCode)
 
 	client.ExecuteQueryOrFail(cliCtx, request, &response)
-	service = response["service"]
+
+	service = response["result"]
+
 	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 := graphql.NewRequest(assets.Read("graphqls/metadata/SearchEndpoints.graphql"))
 	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"]
+	return response["result"]
 }
 
 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 := graphql.NewRequest(assets.Read("graphqls/metadata/Instances.graphql"))
 	request.Var("serviceId", serviceID)
 	request.Var("duration", duration)
 
 	client.ExecuteQueryOrFail(cliCtx, request, &response)
-	return response["instances"]
+
+	return response["result"]
 }
 
 func ServerTimeInfo(cliCtx *cli.Context) (schema.TimeInfo, error) {
 	var response map[string]schema.TimeInfo
-	request := graphql.NewRequest(`
-		query {
-			timeInfo: getTimeInfo {
-				timezone, currentTimestamp
-			}
-		}
-	`)
+
+	request := graphql.NewRequest(assets.Read("graphqls/metadata/ServerTimeInfo.graphql"))
 
 	if err := client.ExecuteQuery(cliCtx, request, &response); err != nil {
 		return schema.TimeInfo{}, err
 	}
-	return response["timeInfo"], nil
+
+	return response["result"], nil
 }
diff --git a/graphql/metrics/metrics.go b/graphql/metrics/metrics.go
index ef71e32..6b12837 100644
--- a/graphql/metrics/metrics.go
+++ b/graphql/metrics/metrics.go
@@ -21,6 +21,8 @@ import (
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/assets"
+
 	"github.com/apache/skywalking-cli/graphql/client"
 
 	"github.com/apache/skywalking-cli/graphql/schema"
@@ -29,72 +31,52 @@ import (
 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 := graphql.NewRequest(assets.Read("graphqls/metadata/IntValues.graphql"))
+
 	request.Var("metric", condition)
 	request.Var("duration", duration)
 
 	client.ExecuteQueryOrFail(ctx, request, &response)
 
-	return response["metrics"]
+	return response["result"]
 }
 
 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 := graphql.NewRequest(assets.Read("graphqls/metadata/LinearIntValues.graphql"))
+
 	request.Var("metric", condition)
 	request.Var("duration", duration)
 
 	client.ExecuteQueryOrFail(ctx, request, &response)
 
-	return response["metrics"]
+	return response["result"]
 }
 
 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 }
-			}
-		}
-	`)
+	var response map[string][]schema.IntValues
+
+	request := graphql.NewRequest(assets.Read("graphqls/metadata/MultipleLinearIntValues.graphql"))
+
 	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"]
+	return response["result"]
 }
 
 func Thermodynamic(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) schema.Thermodynamic {
-	request := graphql.NewRequest(`
-		query ($metric: MetricCondition!, $duration: Duration!) {
-			metrics: getThermodynamic(metric: $metric, duration: $duration) {
-				nodes axisYStep
-			}
-		}
-	`)
+	var response map[string]schema.Thermodynamic
+
+	request := graphql.NewRequest(assets.Read("graphqls/metadata/Thermodynamic.graphql"))
+
 	request.Var("metric", condition)
 	request.Var("duration", duration)
 
-	var response map[string]schema.Thermodynamic
-
 	client.ExecuteQueryOrFail(ctx, request, &response)
 
-	return response["metrics"]
+	return response["result"]
 }
diff --git a/display/graph/graph.go b/graphql/trace/trace.go
similarity index 53%
copy from display/graph/graph.go
copy to graphql/trace/trace.go
index e8fc072..b09a56f 100644
--- a/display/graph/graph.go
+++ b/graphql/trace/trace.go
@@ -15,37 +15,24 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package graph
+package trace
 
 import (
-	"fmt"
-	"reflect"
+	"github.com/machinebox/graphql"
+	"github.com/urfave/cli"
 
-	"github.com/apache/skywalking-cli/display/graph/heatmap"
+	"github.com/apache/skywalking-cli/assets"
+	"github.com/apache/skywalking-cli/graphql/client"
 	"github.com/apache/skywalking-cli/graphql/schema"
-
-	d "github.com/apache/skywalking-cli/display/displayable"
-	"github.com/apache/skywalking-cli/display/graph/linear"
 )
 
-func Display(displayable *d.Displayable) error {
-	data := displayable.Data
-
-	if reflect.TypeOf(data) == reflect.TypeOf(schema.Thermodynamic{}) {
-		return heatmap.Display(displayable)
-	}
-
-	if reflect.TypeOf(data) == reflect.TypeOf(map[string]float64{}) {
-		kvs := []map[string]float64{data.(map[string]float64)}
-
-		return linear.Display(kvs)
-	}
+func Trace(ctx *cli.Context, traceID string) schema.Trace {
+	var response map[string]schema.Trace
 
-	if reflect.TypeOf(data) == reflect.TypeOf([]map[string]float64{}) {
-		kvs := data.([]map[string]float64)
+	request := graphql.NewRequest(assets.Read("graphqls/trace/Trace.graphql"))
+	request.Var("traceId", traceID)
 
-		return linear.Display(kvs)
-	}
+	client.ExecuteQueryOrFail(ctx, request, &response)
 
-	return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
+	return response["result"]
 }
diff --git a/util/lang.go b/util/lang.go
new file mode 100644
index 0000000..4ba5fec
--- /dev/null
+++ b/util/lang.go
@@ -0,0 +1,26 @@
+// 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 util
+
+type Stringify struct {
+	Str string
+}
+
+func (s Stringify) String() string {
+	return s.Str
+}