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
+}