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/07/27 13:47:44 UTC

[skywalking-cli] branch master updated: Add a subcommand of `swctl dashboard global-metrics` (#46)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6ae6ca0  Add a subcommand of `swctl dashboard global-metrics` (#46)
6ae6ca0 is described below

commit 6ae6ca04ba7cc47c9f69d7d34cb325f4723cf588
Author: Hoshea Jiang <fg...@gmail.com>
AuthorDate: Mon Jul 27 21:47:33 2020 +0800

    Add a subcommand of `swctl dashboard global-metrics` (#46)
---
 README.md                                          |  16 ++
 assets/graphqls/dashboard/HeatMap.graphql          |  29 +++
 .../dashboard/LabeledMetricsValues.graphql         |  27 +++
 assets/graphqls/dashboard/SortMetrics.graphql      |  23 +++
 assets/templates/Dashboard.Global.json             |  69 +++++++
 cmd/main.go                                        |   3 +
 .../display.go => commands/dashboard/dashboard.go  |  44 +----
 commands/dashboard/global/global.go                |  57 ++++++
 commands/dashboard/global/metrics.go               |  63 ++++++
 display/display.go                                 |   2 +-
 display/graph/gauge/gauge.go                       | 218 +++++++++++++++++++++
 display/graph/graph.go                             |  11 +-
 example/Dashboard.Global.json                      |  69 +++++++
 graphql/dashboard/global.go                        | 137 +++++++++++++
 14 files changed, 732 insertions(+), 36 deletions(-)

diff --git a/README.md b/README.md
index 22eef04..3b6de38 100644
--- a/README.md
+++ b/README.md
@@ -263,6 +263,22 @@ Ascii Graph, like coloring in terminal, so please use `json`  or `yaml` instead.
 
 </details>
 
+### `dashboard`
+
+<details>
+
+<summary>dashboard global-metrics [--template=template]</summary>
+
+`dashboard global-metrics` displays global metrics in the form of a dashboard.
+
+| argument | description | default |
+| :--- | :--- | :--- |
+| `--template` | the template file to customize how to display information | `templates/Dashboard.Global.json` |
+
+You can imitate the content of [the default template file](example/Dashboard.Global.json) to customize the dashboard.
+
+</details>
+
 # Use Cases
 
 <details>
diff --git a/assets/graphqls/dashboard/HeatMap.graphql b/assets/graphqls/dashboard/HeatMap.graphql
new file mode 100644
index 0000000..ddbbe81
--- /dev/null
+++ b/assets/graphqls/dashboard/HeatMap.graphql
@@ -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.
+
+query ($condition: MetricsCondition!, $duration: Duration!) {
+    result: readHeatMap(condition: $condition, duration: $duration) {
+        values {
+            id
+            values
+        }
+        buckets {
+            min
+            max
+        }
+    }
+}
diff --git a/assets/graphqls/dashboard/LabeledMetricsValues.graphql b/assets/graphqls/dashboard/LabeledMetricsValues.graphql
new file mode 100644
index 0000000..9b8c2ed
--- /dev/null
+++ b/assets/graphqls/dashboard/LabeledMetricsValues.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 ($condition: MetricsCondition!, $label: [String!]!, $duration: Duration!) {
+    result: readLabeledMetricsValues(condition: $condition, labels: $label, duration: $duration) {
+        label
+        values {
+            values {
+                value
+            }
+        }
+    }
+}
diff --git a/assets/graphqls/dashboard/SortMetrics.graphql b/assets/graphqls/dashboard/SortMetrics.graphql
new file mode 100644
index 0000000..051aff5
--- /dev/null
+++ b/assets/graphqls/dashboard/SortMetrics.graphql
@@ -0,0 +1,23 @@
+# 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 ($condition:TopNCondition!, $duration: Duration!) {
+    result: sortMetrics(condition: $condition, duration: $duration) {
+        name
+        value
+    }
+}
diff --git a/assets/templates/Dashboard.Global.json b/assets/templates/Dashboard.Global.json
new file mode 100644
index 0000000..74e55ba
--- /dev/null
+++ b/assets/templates/Dashboard.Global.json
@@ -0,0 +1,69 @@
+{
+  "metrics": [
+    {
+      "condition": {
+        "name": "service_cpm",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": " Service Load (calls/min) "
+    },
+    {
+      "condition": {
+        "name": "service_resp_time",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": "    Slow Services (ms)    "
+    },
+    {
+      "condition": {
+        "name": "service_apdex",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "ASC"
+      },
+      "title": "Un-Health Services (Apdex)",
+      "aggregation": "/",
+      "aggregationNum": "10000"
+    },
+    {
+      "condition": {
+        "name": "endpoint_avg",
+        "normal": true,
+        "scope": "Endpoint",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": "    Slow Endpoints (ms)   "
+    }
+  ],
+  "responseLatency": {
+    "condition": {
+      "name": "all_percentile",
+      "entity": {
+        "scope": "All",
+        "normal": true
+      }
+    },
+    "labels": "0, 1, 2, 3, 4",
+    "title": "Global Response Latency",
+    "unit": "percentile in ms"
+  },
+  "heatMap": {
+    "condition": {
+      "name": "all_heatmap",
+      "entity": {
+        "scope": "All",
+        "normal": true
+      }
+    },
+    "title": "Global Heatmap",
+    "unit": "ms"
+  }
+}
diff --git a/cmd/main.go b/cmd/main.go
index 74ec77e..40e81ae 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -24,6 +24,8 @@ import (
 	"github.com/apache/skywalking-cli/commands/common"
 	"github.com/apache/skywalking-cli/commands/trace"
 
+	"github.com/apache/skywalking-cli/commands/dashboard"
+
 	"github.com/apache/skywalking-cli/commands/metrics"
 
 	"github.com/apache/skywalking-cli/commands/endpoint"
@@ -89,6 +91,7 @@ func main() {
 		metrics.Command,
 		trace.Command,
 		common.Command,
+		dashboard.Command,
 	}
 
 	app.Before = interceptor.BeforeChain([]cli.BeforeFunc{
diff --git a/display/display.go b/commands/dashboard/dashboard.go
similarity index 50%
copy from display/display.go
copy to commands/dashboard/dashboard.go
index af3a598..cb1c7c7 100644
--- a/display/display.go
+++ b/commands/dashboard/dashboard.go
@@ -15,44 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package display
+package dashboard
 
 import (
-	"fmt"
-	"strings"
-
-	d "github.com/apache/skywalking-cli/display/displayable"
-
-	"github.com/apache/skywalking-cli/display/graph"
-
 	"github.com/urfave/cli"
 
-	"github.com/apache/skywalking-cli/display/json"
-	"github.com/apache/skywalking-cli/display/table"
-	"github.com/apache/skywalking-cli/display/yaml"
+	"github.com/apache/skywalking-cli/commands/dashboard/global"
 )
 
-const (
-	JSON  = "json"
-	YAML  = "yaml"
-	TABLE = "table"
-	GRAPH = "graph"
-)
-
-// Display the object in the style specified in flag --display
-func Display(ctx *cli.Context, displayable *d.Displayable) error {
-	displayStyle := ctx.GlobalString("display")
-
-	switch strings.ToLower(displayStyle) {
-	case JSON:
-		return json.Display(displayable)
-	case YAML:
-		return yaml.Display(displayable)
-	case TABLE:
-		return table.Display(displayable)
-	case GRAPH:
-		return graph.Display(displayable)
-	default:
-		return fmt.Errorf("unsupported display style: %s", displayStyle)
-	}
+var Command = cli.Command{
+	Name:      "dashboard",
+	ShortName: "db",
+	Usage:     "Dashboard related sub-command",
+	Subcommands: cli.Commands{
+		global.GlobalCommand,
+		global.Metrics,
+	},
 }
diff --git a/commands/dashboard/global/global.go b/commands/dashboard/global/global.go
new file mode 100644
index 0000000..2a1bc04
--- /dev/null
+++ b/commands/dashboard/global/global.go
@@ -0,0 +1,57 @@
+// 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 global
+
+import (
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/commands/model"
+	"github.com/apache/skywalking-cli/graphql/schema"
+
+	"github.com/apache/skywalking-cli/display/displayable"
+
+	"github.com/apache/skywalking-cli/commands/flags"
+	"github.com/apache/skywalking-cli/commands/interceptor"
+	"github.com/apache/skywalking-cli/display"
+	"github.com/apache/skywalking-cli/graphql/dashboard"
+)
+
+var GlobalCommand = cli.Command{
+	Name:        "global",
+	ShortName:   "g",
+	Usage:       "Display global data",
+	Description: "Display global data",
+	Flags:       flags.DurationFlags,
+	Before: interceptor.BeforeChain([]cli.BeforeFunc{
+		interceptor.TimezoneInterceptor,
+		interceptor.DurationInterceptor,
+	}),
+	Action: func(ctx *cli.Context) error {
+		end := ctx.String("end")
+		start := ctx.String("start")
+		step := ctx.Generic("step")
+
+		globalData := dashboard.Global(ctx, schema.Duration{
+			Start: start,
+			End:   end,
+			Step:  step.(*model.StepEnumValue).Selected,
+		})
+
+		return display.Display(ctx, &displayable.Displayable{Data: globalData})
+	},
+}
diff --git a/commands/dashboard/global/metrics.go b/commands/dashboard/global/metrics.go
new file mode 100644
index 0000000..d5ffa40
--- /dev/null
+++ b/commands/dashboard/global/metrics.go
@@ -0,0 +1,63 @@
+// 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 global
+
+import (
+	"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/display/displayable"
+	"github.com/apache/skywalking-cli/graphql/dashboard"
+	"github.com/apache/skywalking-cli/graphql/schema"
+
+	"github.com/urfave/cli"
+)
+
+var Metrics = cli.Command{
+	Name:  "global-metrics",
+	Usage: "Query global metrics",
+	Flags: flags.Flags(
+		flags.DurationFlags,
+		[]cli.Flag{
+			cli.StringFlag{
+				Name:     "template",
+				Usage:    "load dashboard UI template",
+				Required: false,
+				Value:    dashboard.DefaultTemplatePath,
+			},
+		},
+	),
+	Before: interceptor.BeforeChain([]cli.BeforeFunc{
+		interceptor.TimezoneInterceptor,
+		interceptor.DurationInterceptor,
+	}),
+	Action: func(ctx *cli.Context) error {
+		end := ctx.String("end")
+		start := ctx.String("start")
+		step := ctx.Generic("step")
+
+		globalMetrics := dashboard.Metrics(ctx, schema.Duration{
+			Start: start,
+			End:   end,
+			Step:  step.(*model.StepEnumValue).Selected,
+		})
+
+		return display.Display(ctx, &displayable.Displayable{Data: globalMetrics})
+	},
+}
diff --git a/display/display.go b/display/display.go
index af3a598..9e7d132 100644
--- a/display/display.go
+++ b/display/display.go
@@ -51,7 +51,7 @@ func Display(ctx *cli.Context, displayable *d.Displayable) error {
 	case TABLE:
 		return table.Display(displayable)
 	case GRAPH:
-		return graph.Display(displayable)
+		return graph.Display(ctx, displayable)
 	default:
 		return fmt.Errorf("unsupported display style: %s", displayStyle)
 	}
diff --git a/display/graph/gauge/gauge.go b/display/graph/gauge/gauge.go
new file mode 100644
index 0000000..4edf51c
--- /dev/null
+++ b/display/graph/gauge/gauge.go
@@ -0,0 +1,218 @@
+// 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 gauge
+
+import (
+	"context"
+	"fmt"
+	"math"
+	"strconv"
+	"strings"
+
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/graphql/dashboard"
+	"github.com/apache/skywalking-cli/graphql/schema"
+
+	"github.com/mum4k/termdash"
+	"github.com/mum4k/termdash/cell"
+	"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/gauge"
+	"github.com/mum4k/termdash/widgets/text"
+)
+
+const RootID = "root"
+
+type metricColumn struct {
+	title  *text.Text
+	gauges []*gauge.Gauge
+}
+
+func newMetricColumn(column []*schema.SelectedRecord, config *dashboard.MetricTemplate) (*metricColumn, error) {
+	var ret metricColumn
+	var maxValue int
+
+	t, err := text.New()
+	if err != nil {
+		return nil, err
+	}
+	if err := t.Write(config.Title, text.WriteCellOpts(cell.FgColor(cell.ColorRed))); err != nil {
+		return nil, err
+	}
+	ret.title = t
+
+	if config.Condition.Order == schema.OrderDes {
+		temp, err := strconv.Atoi(*(column[0].Value))
+		if err != nil {
+			return nil, err
+		}
+		maxValue = temp
+	} else if config.Condition.Order == schema.OrderAsc {
+		temp, err := strconv.Atoi(*(column[len(column)-1].Value))
+		if err != nil {
+			return nil, err
+		}
+		maxValue = temp
+	}
+
+	for _, item := range column {
+		strValue := *(item.Value)
+		v, err := strconv.Atoi(strValue)
+		if err != nil {
+			return nil, err
+		}
+
+		if config.AggregationNum != "" {
+			aggregationNum, convErr := strconv.Atoi(config.AggregationNum)
+			if convErr != nil {
+				return nil, convErr
+			}
+			strValue = fmt.Sprintf("%.4f", float64(v)/float64(aggregationNum))
+		}
+
+		g, err := gauge.New(
+			gauge.Height(1),
+			gauge.Border(linestyle.Light),
+			gauge.Color(cell.ColorMagenta),
+			gauge.BorderTitle("["+strValue+"]"),
+			gauge.HideTextProgress(),
+			gauge.TextLabel(item.Name),
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := g.Absolute(v, maxValue); err != nil {
+			return nil, err
+		}
+		ret.gauges = append(ret.gauges, g)
+	}
+
+	return &ret, nil
+}
+
+func layout(columns ...*metricColumn) ([]container.Option, error) {
+	var metricColumns []grid.Element
+	var columnWidthPerc int
+
+	// For the best display effect, the maximum number of columns that can be displayed
+	const MaxColumnNum = 4
+	// For the best display effect, the maximum number of gauges
+	// that can be displayed in each column
+	const MaxGaugeNum = 6
+	const TitleHeight = 10
+
+	// Number of columns to display, each column represents a global metric
+	// The number should be less than or equal to MaxColumnNum
+	columnNum := int(math.Min(MaxColumnNum, float64(len(columns))))
+
+	// columnWidthPerc should be in the range (0, 100)
+	if columnNum > 1 {
+		columnWidthPerc = 100 / columnNum
+	} else {
+		columnWidthPerc = 99
+	}
+
+	for i := 0; i < columnNum; i++ {
+		var column []grid.Element
+		column = append(column, grid.RowHeightPerc(TitleHeight, grid.Widget(columns[i].title)))
+
+		// Number of gauge in a column, each gauge represents a service or endpoint
+		// The number should be less than or equal to MaxGaugeNum
+		gaugeNum := int(math.Min(MaxGaugeNum, float64(len(columns[i].gauges))))
+		gaugeHeight := int(math.Floor(float64(100-TitleHeight) / float64(gaugeNum)))
+
+		for j := 0; j < gaugeNum; j++ {
+			column = append(column, grid.RowHeightPerc(gaugeHeight, grid.Widget(columns[i].gauges[j])))
+		}
+		metricColumns = append(metricColumns, grid.ColWidthPerc(columnWidthPerc, column...))
+	}
+
+	builder := grid.New()
+	builder.Add(
+		grid.RowHeightPerc(10),
+		grid.RowHeightPerc(80, metricColumns...),
+	)
+
+	gridOpts, err := builder.Build()
+	if err != nil {
+		return nil, err
+	}
+	return gridOpts, nil
+}
+
+func Display(ctx *cli.Context, metrics [][]*schema.SelectedRecord) error {
+	t, err := termbox.New()
+	if err != nil {
+		return err
+	}
+	defer t.Close()
+
+	c, err := container.New(
+		t,
+		container.ID(RootID),
+	)
+	if err != nil {
+		return err
+	}
+
+	var columns []*metricColumn
+
+	configs, err := dashboard.LoadTemplate(ctx.String("template"))
+	if err != nil {
+		return nil
+	}
+
+	for i, config := range configs.Metrics {
+		col, innerErr := newMetricColumn(metrics[i], &config)
+		if innerErr != nil {
+			return innerErr
+		}
+		columns = append(columns, col)
+	}
+
+	gridOpts, err := layout(columns...)
+	if err != nil {
+		return err
+	}
+
+	err = c.Update(RootID, append(
+		gridOpts,
+		container.Border(linestyle.Light),
+		container.BorderTitle("[Global Metrics]-PRESS Q TO QUIT"))...,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	con, cancel := context.WithCancel(context.Background())
+	quitter := func(keyboard *terminalapi.Keyboard) {
+		if strings.EqualFold(keyboard.Key.String(), "q") {
+			cancel()
+		}
+	}
+
+	err = termdash.Run(con, t, c, termdash.KeyboardSubscriber(quitter))
+
+	return err
+}
diff --git a/display/graph/graph.go b/display/graph/graph.go
index f951b51..c683c27 100644
--- a/display/graph/graph.go
+++ b/display/graph/graph.go
@@ -21,6 +21,10 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/display/graph/gauge"
+
 	"github.com/apache/skywalking-cli/display/graph/tree"
 
 	"github.com/apache/skywalking-cli/display/graph/heatmap"
@@ -35,6 +39,7 @@ type (
 	LinearMetrics      = map[string]float64
 	MultiLinearMetrics = []LinearMetrics
 	Trace              = schema.Trace
+	GlobalMetrics      = [][]*schema.SelectedRecord
 )
 
 var (
@@ -42,9 +47,10 @@ var (
 	LinearMetricsType      = reflect.TypeOf(LinearMetrics{})
 	MultiLinearMetricsType = reflect.TypeOf(MultiLinearMetrics{})
 	TraceType              = reflect.TypeOf(Trace{})
+	GlobalMetricsType      = reflect.TypeOf(GlobalMetrics{})
 )
 
-func Display(displayable *d.Displayable) error {
+func Display(ctx *cli.Context, displayable *d.Displayable) error {
 	data := displayable.Data
 
 	switch reflect.TypeOf(data) {
@@ -60,6 +66,9 @@ func Display(displayable *d.Displayable) error {
 	case TraceType:
 		return tree.Display(tree.Adapt(data.(Trace)))
 
+	case GlobalMetricsType:
+		return gauge.Display(ctx, data.(GlobalMetrics))
+
 	default:
 		return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
 	}
diff --git a/example/Dashboard.Global.json b/example/Dashboard.Global.json
new file mode 100644
index 0000000..74e55ba
--- /dev/null
+++ b/example/Dashboard.Global.json
@@ -0,0 +1,69 @@
+{
+  "metrics": [
+    {
+      "condition": {
+        "name": "service_cpm",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": " Service Load (calls/min) "
+    },
+    {
+      "condition": {
+        "name": "service_resp_time",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": "    Slow Services (ms)    "
+    },
+    {
+      "condition": {
+        "name": "service_apdex",
+        "normal": true,
+        "scope": "Service",
+        "topN": 10,
+        "order": "ASC"
+      },
+      "title": "Un-Health Services (Apdex)",
+      "aggregation": "/",
+      "aggregationNum": "10000"
+    },
+    {
+      "condition": {
+        "name": "endpoint_avg",
+        "normal": true,
+        "scope": "Endpoint",
+        "topN": 10,
+        "order": "DES"
+      },
+      "title": "    Slow Endpoints (ms)   "
+    }
+  ],
+  "responseLatency": {
+    "condition": {
+      "name": "all_percentile",
+      "entity": {
+        "scope": "All",
+        "normal": true
+      }
+    },
+    "labels": "0, 1, 2, 3, 4",
+    "title": "Global Response Latency",
+    "unit": "percentile in ms"
+  },
+  "heatMap": {
+    "condition": {
+      "name": "all_heatmap",
+      "entity": {
+        "scope": "All",
+        "normal": true
+      }
+    },
+    "title": "Global Heatmap",
+    "unit": "ms"
+  }
+}
diff --git a/graphql/dashboard/global.go b/graphql/dashboard/global.go
new file mode 100644
index 0000000..eb8cf13
--- /dev/null
+++ b/graphql/dashboard/global.go
@@ -0,0 +1,137 @@
+// 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 dashboard
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+
+	"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"
+)
+
+type MetricTemplate struct {
+	Condition      schema.TopNCondition `json:"condition"`
+	Title          string               `json:"title"`
+	Aggregation    string               `json:"aggregation"`
+	AggregationNum string               `json:"aggregationNum"`
+}
+
+type ChartTemplate struct {
+	Condition schema.MetricsCondition `json:"condition"`
+	Title     string                  `json:"title"`
+	Unit      string                  `json:"unit"`
+	Labels    string                  `json:"labels"`
+}
+
+type GlobalTemplate struct {
+	Metrics         []MetricTemplate `json:"metrics"`
+	ResponseLatency ChartTemplate    `json:"responseLatency"`
+	HeatMap         ChartTemplate    `json:"heatMap"`
+}
+
+type GlobalData struct {
+	Metrics         [][]*schema.SelectedRecord `json:"metrics"`
+	ResponseLatency []*schema.MetricsValues    `json:"responseLatency"`
+	HeatMap         schema.HeatMap             `json:"heatMap"`
+}
+
+const DefaultTemplatePath = "templates/Dashboard.Global.json"
+
+func LoadTemplate(filename string) (*GlobalTemplate, error) {
+	var config GlobalTemplate
+	var byteValue []byte
+
+	if filename == DefaultTemplatePath {
+		jsonFile := assets.Read(filename)
+		byteValue = []byte(jsonFile)
+	} else {
+		jsonFile, err := os.Open(filename)
+		if err != nil {
+			return nil, err
+		}
+		defer jsonFile.Close()
+
+		byteValue, err = ioutil.ReadAll(jsonFile)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if err := json.Unmarshal(byteValue, &config); err != nil {
+		return nil, err
+	}
+	return &config, nil
+}
+
+func Metrics(ctx *cli.Context, duration schema.Duration) [][]*schema.SelectedRecord {
+	var ret [][]*schema.SelectedRecord
+	configs, err := LoadTemplate(ctx.String("template"))
+	if err != nil {
+		return nil
+	}
+
+	for _, m := range configs.Metrics {
+		var response map[string][]*schema.SelectedRecord
+		request := graphql.NewRequest(assets.Read("graphqls/dashboard/SortMetrics.graphql"))
+		request.Var("condition", m.Condition)
+		request.Var("duration", duration)
+
+		client.ExecuteQueryOrFail(ctx, request, &response)
+		ret = append(ret, response["result"])
+	}
+
+	return ret
+}
+
+func responseLatency(ctx *cli.Context, duration schema.Duration) []*schema.MetricsValues {
+	var response map[string][]*schema.MetricsValues
+
+	request := graphql.NewRequest(assets.Read("graphqls/dashboard/LabeledMetricsValues.graphql"))
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func heatMap(ctx *cli.Context, duration schema.Duration) schema.HeatMap {
+	var response map[string]schema.HeatMap
+
+	request := graphql.NewRequest(assets.Read("graphqls/dashboard/HeatMap.graphql"))
+	request.Var("duration", duration)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func Global(ctx *cli.Context, duration schema.Duration) *GlobalData {
+	var globalData GlobalData
+
+	globalData.Metrics = Metrics(ctx, duration)
+	globalData.ResponseLatency = responseLatency(ctx, duration)
+	globalData.HeatMap = heatMap(ctx, duration)
+
+	return &globalData
+}