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/08/29 15:45:35 UTC
[skywalking-cli] branch master updated: Refactor `metrics
thermodynamic` command (#59)
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 b47c5fb Refactor `metrics thermodynamic` command (#59)
b47c5fb is described below
commit b47c5fb913c7e9e2472ae4837880510a8780731c
Author: Hoshea Jiang <fg...@gmail.com>
AuthorDate: Sat Aug 29 23:45:28 2020 +0800
Refactor `metrics thermodynamic` command (#59)
---
README.md | 6 +-
assets/graphqls/metrics/Thermodynamic.graphql | 13 ++-
commands/metrics/thermodynamic/thermodynamic.go | 31 +++---
commands/model/scope.go | 52 +++++++++
display/graph/graph.go | 2 +-
display/graph/heatmap/heatmap.go | 140 ++++++++++++------------
graphql/metrics/metrics.go | 6 +-
lib/heatmap.go | 114 -------------------
8 files changed, 156 insertions(+), 208 deletions(-)
diff --git a/README.md b/README.md
index 3a96ab5..4c634c8 100644
--- a/README.md
+++ b/README.md
@@ -222,12 +222,12 @@ Ascii Graph, like coloring in terminal, so please use `json` or `yaml` instead.
<details>
-<summary>metrics thermodynamic --name=thermodynamic name --ids=entity-ids</summary>
+<summary>metrics thermodynamic --name=thermodynamic name [--scope=scope-of-metrics]</summary>
| option | description | default |
| :--- | :--- | :--- |
| `--name` | Metrics name, defined in [OAL](https://github.com/apache/skywalking/blob/master/oap-server/server-bootstrap/src/main/resources/official_analysis.oal), such as `service_sla`, etc. |
-| `--ids` | IDs that are required by the metric type, such as service IDs for `service_heatmap` |
+| `--scope` | The scope of metrics, which is consistent with `--name`, such as `All`, `Service`, etc. |`All`|
| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
@@ -567,7 +567,7 @@ $ ./bin/swctl metrics top 5 --name endpoint_sla --service-id 3
```shell
$ ./bin/swctl metrics thermodynamic --name all_heatmap
-{"nodes":[[0,0,238],[0,1,1],[0,2,39],[0,3,31],[0,4,12],[0,5,13],[0,6,4],[0,7,3],[0,8,3],[0,9,0],[0,10,48],[0,11,3],[0,12,49],[0,13,54],[0,14,11],[0,15,9],[0,16,2],[0,17,4],[0,18,0],[0,19,1],[0,20,186],[1,0,264],[1,1,3],[1,2,51],[1,3,38],[1,4,16],[1,5,14],[1,6,3],[1,7,2],[1,8,1],[1,9,2],[1,10,51],[1,11,1],[1,12,41],[1,13,56],[1,14,16],[1,15,15],[1,16,7],[1,17,7],[1,18,3],[1,19,1],[1,20,174],[2,0,231],[2,1,3],[2,2,42],[2,3,41],[2,4,18],[2,5,4],[2,6,2],[2,7,1],[2,8,2],[2,9,0],[2,10,54],[2,1 [...]
+{"values":[{"id":"202008290939","values":[473,3,0,0,0,0,0,0,0,0,323,0,4,0,0,0,0,0,0,0,436]},{"id":"202008290940","values":[434,0,0,0,0,0,0,0,0,0,367,0,4,0,0,0,0,0,0,0,427]},{"id":"202008290941","values":[504,0,0,0,0,0,0,0,0,0,410,0,5,0,1,0,0,0,0,0,377]},{"id":"202008290942","values":[445,0,4,0,0,0,0,0,0,0,350,0,0,0,0,0,0,0,0,0,420]},{"id":"202008290943","values":[436,0,1,0,0,0,0,0,0,0,367,0,3,0,0,0,0,0,0,0,404]},{"id":"202008290944","values":[463,0,0,0,0,0,0,0,0,0,353,0,0,0,0,0,0,0,0,0,4 [...]
```
```shell
diff --git a/assets/graphqls/metrics/Thermodynamic.graphql b/assets/graphqls/metrics/Thermodynamic.graphql
index 9ff50bd..ddbbe81 100644
--- a/assets/graphqls/metrics/Thermodynamic.graphql
+++ b/assets/graphqls/metrics/Thermodynamic.graphql
@@ -15,8 +15,15 @@
# specific language governing permissions and limitations
# under the License.
-query ($metric: MetricCondition!, $duration: Duration!) {
- result: getThermodynamic(metric: $metric, duration: $duration) {
- nodes axisYStep
+query ($condition: MetricsCondition!, $duration: Duration!) {
+ result: readHeatMap(condition: $condition, duration: $duration) {
+ values {
+ id
+ values
+ }
+ buckets {
+ min
+ max
+ }
}
}
diff --git a/commands/metrics/thermodynamic/thermodynamic.go b/commands/metrics/thermodynamic/thermodynamic.go
index 096a921..62268e1 100644
--- a/commands/metrics/thermodynamic/thermodynamic.go
+++ b/commands/metrics/thermodynamic/thermodynamic.go
@@ -31,9 +31,9 @@ import (
)
var Command = cli.Command{
- Name: "thermodynamic",
- ShortName: "td",
- Usage: "Query thermodynamic metrics defined in backend OAL",
+ Name: "thermodynamic",
+ Aliases: []string{"td", "heatmap", "hp"},
+ Usage: "Query thermodynamic metrics defined in backend OAL",
Flags: flags.Flags(
flags.DurationFlags,
[]cli.Flag{
@@ -42,10 +42,14 @@ var Command = cli.Command{
Usage: "metrics `name`, which should be defined in OAL script",
Required: true,
},
- cli.StringFlag{
- Name: "id",
- Usage: "metrics `id` if the metrics require one",
- Required: false,
+ cli.GenericFlag{
+ Name: "scope",
+ Usage: "the scope of the query, which follows the metrics `name`",
+ Value: &model.ScopeEnumValue{
+ Enum: schema.AllScope,
+ Default: schema.ScopeAll,
+ Selected: schema.ScopeAll,
+ },
},
},
),
@@ -58,12 +62,7 @@ var Command = cli.Command{
start := ctx.String("start")
step := ctx.Generic("step")
metricsName := ctx.String("name")
-
- var id *string = nil
- if ctx.String("id") != "" {
- idString := ctx.String("id")
- id = &idString
- }
+ scope := ctx.Generic("scope").(*model.ScopeEnumValue).Selected
duration := schema.Duration{
Start: start,
@@ -71,9 +70,11 @@ var Command = cli.Command{
Step: step.(*model.StepEnumValue).Selected,
}
- metricsValues := metrics.Thermodynamic(ctx, schema.MetricCondition{
+ metricsValues := metrics.Thermodynamic(ctx, schema.MetricsCondition{
Name: metricsName,
- ID: id,
+ Entity: &schema.Entity{
+ Scope: scope,
+ },
}, duration)
return display.Display(ctx, &displayable.Displayable{
diff --git a/commands/model/scope.go b/commands/model/scope.go
new file mode 100644
index 0000000..7adc5e2
--- /dev/null
+++ b/commands/model/scope.go
@@ -0,0 +1,52 @@
+// 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 model
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/apache/skywalking-cli/graphql/schema"
+)
+
+// ScopeEnumValue defines the values domain of --scope option
+type ScopeEnumValue struct {
+ Enum []schema.Scope
+ Default schema.Scope
+ Selected schema.Scope
+}
+
+// Set the --scope value, from raw string to ScopeEnumValue
+func (s *ScopeEnumValue) Set(value string) error {
+ for _, enum := range s.Enum {
+ if strings.EqualFold(enum.String(), value) {
+ s.Selected = enum
+ return nil
+ }
+ }
+ scopes := make([]string, len(schema.AllScope))
+ for i, scope := range schema.AllScope {
+ scopes[i] = scope.String()
+ }
+ return fmt.Errorf("allowed scopes are %s", strings.Join(scopes, ", "))
+}
+
+// String representation of the scope
+func (s ScopeEnumValue) String() string {
+ return s.Selected.String()
+}
diff --git a/display/graph/graph.go b/display/graph/graph.go
index ddc37da..73dd51e 100644
--- a/display/graph/graph.go
+++ b/display/graph/graph.go
@@ -36,7 +36,7 @@ import (
)
type (
- Thermodynamic = schema.Thermodynamic
+ Thermodynamic = schema.HeatMap
LinearMetrics = map[string]float64
MultiLinearMetrics = []LinearMetrics
Trace = schema.Trace
diff --git a/display/graph/heatmap/heatmap.go b/display/graph/heatmap/heatmap.go
index 0ca653a..1a40d29 100644
--- a/display/graph/heatmap/heatmap.go
+++ b/display/graph/heatmap/heatmap.go
@@ -18,97 +18,99 @@
package heatmap
import (
+ "context"
"fmt"
- "math"
- "time"
+ "strings"
- "github.com/apache/skywalking-cli/graphql/utils"
- "github.com/apache/skywalking-cli/util"
-
- ui "github.com/gizak/termui/v3"
+ "github.com/mum4k/termdash"
+ "github.com/mum4k/termdash/container"
+ "github.com/mum4k/termdash/container/grid"
+ "github.com/mum4k/termdash/linestyle"
+ "github.com/mum4k/termdash/terminal/termbox"
+ "github.com/mum4k/termdash/terminal/terminalapi"
+ "github.com/mum4k/termdash/widgetapi"
d "github.com/apache/skywalking-cli/display/displayable"
"github.com/apache/skywalking-cli/graphql/schema"
- "github.com/apache/skywalking-cli/lib"
+ "github.com/apache/skywalking-cli/graphql/utils"
+ "github.com/apache/skywalking-cli/lib/heatmap"
)
-func Display(displayable *d.Displayable) error {
- data := displayable.Data.(schema.Thermodynamic)
-
- nodes := data.Nodes
- duration := displayable.Duration
-
- rows, cols, min, max := statistics(nodes)
+const rootID = "root"
- if err := ui.Init(); err != nil {
- return err
+func NewHeatMapWidget(data schema.HeatMap) (hp *heatmap.HeatMap, err error) {
+ hp, err = heatmap.NewHeatMap()
+ if err != nil {
+ return hp, err
}
- defer ui.Close()
- termW, _ := ui.TerminalDimensions()
+ hpColumns := utils.HeatMapToMap(&data)
+ yLabels := utils.BucketsToStrings(data.Buckets)
+ hp.SetColumns(hpColumns)
+ hp.SetYLabels(yLabels)
- hm := lib.NewHeatMap()
- hm.Title = fmt.Sprintf(" %s ", displayable.Title)
- hm.XLabels = make([]string, rows)
- hm.YLabels = make([]string, cols)
- for i := 0; i < rows; i++ {
- step := utils.StepDuration[duration.Step]
- format := utils.StepFormats[duration.Step]
- startTime, err := time.Parse(format, duration.Start)
+ return
+}
- if err != nil {
- return err
- }
+// layout controls where and how the heat map widget is placed.
+// Here uses the grid layout to center the widget horizontally and vertically.
+func layout(hp widgetapi.Widget) ([]container.Option, error) {
+ const hpColWidthPerc = 85
+ const hpRowHeightPerc = 80
+
+ builder := grid.New()
+ builder.Add(
+ grid.ColWidthPerc((99-hpColWidthPerc)/2), // Use two empty cols to center the heatmap.
+ grid.ColWidthPerc(hpColWidthPerc,
+ grid.RowHeightPerc((99-hpRowHeightPerc)/2), // Use two empty rows to center the heatmap.
+ grid.RowHeightPerc(hpRowHeightPerc, grid.Widget(hp)),
+ grid.RowHeightPerc((99-hpRowHeightPerc)/2),
+ ),
+ grid.ColWidthPerc((99-hpColWidthPerc)/2),
+ )
+ return builder.Build()
+}
- hm.XLabels[i] = startTime.Add(time.Duration(i) * step).Format("15:04")
+func Display(displayable *d.Displayable) error {
+ t, err := termbox.New(termbox.ColorMode(terminalapi.ColorMode256))
+ if err != nil {
+ return err
}
- for i := 0; i < cols; i++ {
- hm.YLabels[i] = fmt.Sprintf("%4d", i*data.AxisYStep)
+ defer t.Close()
+
+ title := fmt.Sprintf("[%s]-PRESS Q TO QUIT", displayable.Title)
+ c, err := container.New(
+ t,
+ container.Border(linestyle.Light),
+ container.BorderTitle(title),
+ container.ID(rootID))
+ if err != nil {
+ return err
}
- hm.Data = make([][]float64, rows)
- hm.CellColors = make([][]ui.Color, rows)
- hm.NumStyles = make([][]ui.Style, rows)
- for row := 0; row < rows; row++ {
- hm.Data[row] = make([]float64, cols)
- hm.CellColors[row] = make([]ui.Color, cols)
- hm.NumStyles[row] = make([]ui.Style, cols)
+ data := displayable.Data.(schema.HeatMap)
+ hp, err := NewHeatMapWidget(data)
+ if err != nil {
+ return err
}
- scale := max - min
- for _, node := range nodes {
- color := ui.Color(255 - (float64(*node[2])/scale)*23)
- hm.Data[*node[0]][*node[1]] = float64(*node[2])
- hm.CellColors[*node[0]][*node[1]] = color
- hm.NumStyles[*node[0]][*node[1]] = ui.Style{Fg: ui.ColorMagenta}
+ gridOpts, err := layout(hp)
+ if err != nil {
+ return fmt.Errorf("builder.Build => %v", err)
}
- hm.Formatter = nil
- hm.XLabelStyles = []ui.Style{{Fg: ui.ColorWhite}}
- hm.CellGap = 0
- hm.CellWidth = int(float64(termW) / float64(rows))
- realWidth := (hm.CellWidth+hm.CellGap)*(rows+1) - hm.CellGap + 5
- hm.SetRect(int(float64(termW-realWidth)/2), 2, realWidth, cols+5)
-
- ui.Render(hm)
-
- events := ui.PollEvents()
- for e := <-events; e.ID != "q" && e.ID != "<C-c>"; e = <-events {
+ if e := c.Update(rootID, gridOpts...); e != nil {
+ return e
}
- return nil
-}
-
-func statistics(nodes [][]*int) (rows, cols int, min, max float64) {
- min = math.MaxFloat64
- for _, node := range nodes {
- rows = util.MaxInt(rows, *node[0])
- cols = util.MaxInt(cols, *node[1])
- max = math.Max(max, float64(*node[2]))
- min = math.Min(min, float64(*node[2]))
+ con, cancel := context.WithCancel(context.Background())
+ quitter := func(keyboard *terminalapi.Keyboard) {
+ if strings.EqualFold(keyboard.Key.String(), "q") {
+ cancel()
+ }
}
- rows++
- cols++
- return
+ err = termdash.Run(con, t, c, termdash.KeyboardSubscriber(quitter))
+
+ return err
}
diff --git a/graphql/metrics/metrics.go b/graphql/metrics/metrics.go
index 70a0422..0cf7099 100644
--- a/graphql/metrics/metrics.go
+++ b/graphql/metrics/metrics.go
@@ -68,12 +68,12 @@ func MultipleLinearIntValues(ctx *cli.Context, condition schema.MetricCondition,
return response["result"]
}
-func Thermodynamic(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) schema.Thermodynamic {
- var response map[string]schema.Thermodynamic
+func Thermodynamic(ctx *cli.Context, condition schema.MetricsCondition, duration schema.Duration) schema.HeatMap {
+ var response map[string]schema.HeatMap
request := graphql.NewRequest(assets.Read("graphqls/metrics/Thermodynamic.graphql"))
- request.Var("metric", condition)
+ request.Var("condition", condition)
request.Var("duration", duration)
client.ExecuteQueryOrFail(ctx, request, &response)
diff --git a/lib/heatmap.go b/lib/heatmap.go
deleted file mode 100644
index bf24b86..0000000
--- a/lib/heatmap.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// 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 lib
-
-import (
- "fmt"
- im "image"
-
- ui "github.com/gizak/termui/v3"
- rw "github.com/mattn/go-runewidth"
-)
-
-type HeatMap struct {
- ui.Block
- XLabelStyles []ui.Style
- CellColors [][]ui.Color
- NumStyles [][]ui.Style
- Formatter func(float64) string
- Data [][]float64
- XLabels []string
- YLabels []string
- CellWidth int
- CellGap int
-}
-
-func NewHeatMap() *HeatMap {
- return &HeatMap{
- Block: *ui.NewBlock(),
- CellColors: [][]ui.Color{ui.StandardColors, ui.StandardColors},
- NumStyles: [][]ui.Style{ui.StandardStyles, ui.StandardStyles},
- Formatter: func(n float64) string { return fmt.Sprint(n) },
- XLabelStyles: ui.StandardStyles,
- CellGap: 1,
- CellWidth: 3,
- }
-}
-
-func (hm *HeatMap) Draw(buffer *ui.Buffer) {
- hm.Block.Draw(buffer)
-
- cellX := hm.Inner.Min.X
-
- for i, column := range hm.Data {
- cellY := 0
- for j, datum := range column {
- buffer.SetString(
- hm.YLabels[j],
- ui.StyleClear,
- im.Pt(hm.Inner.Min.X, (hm.Inner.Max.Y-2)-cellY),
- )
- for x := cellX + 5; x < ui.MinInt(cellX+hm.CellWidth, hm.Inner.Max.X)+5; x++ {
- for y := (hm.Inner.Max.Y - 2) - cellY; y > (hm.Inner.Max.Y-2)-cellY-1; y-- {
- cell := ui.NewCell(' ', ui.NewStyle(ui.ColorClear, color(hm.CellColors, i, j)))
- buffer.SetCell(cell, im.Pt(x, y))
- }
- }
-
- if hm.Formatter != nil {
- hm.drawNumber(buffer, datum, i, j, cellX+5, cellY)
- }
-
- cellY++
- }
-
- if i < len(hm.XLabels) {
- hm.drawLabel(buffer, cellX+5, i)
- }
-
- cellX += hm.CellWidth + hm.CellGap
- }
-}
-
-func (hm *HeatMap) drawLabel(buffer *ui.Buffer, cellX, i int) {
- labelX := cellX + ui.MaxInt(int(float64(hm.CellWidth)/2)-int(float64(rw.StringWidth(hm.XLabels[i]))/2), 0)
- buffer.SetString(
- ui.TrimString(hm.XLabels[i], hm.CellWidth),
- ui.SelectStyle(hm.XLabelStyles, i),
- im.Pt(labelX, hm.Inner.Max.Y-1),
- )
-}
-
-func (hm *HeatMap) drawNumber(buffer *ui.Buffer, datum float64, i, j, cellX, cellY int) {
- x := cellX + int(float64(hm.CellWidth)/2) - 1
- numberStyle := style(hm.NumStyles, i, j)
- cellColor := color(hm.CellColors, i, j)
- buffer.SetString(
- hm.Formatter(datum),
- ui.NewStyle(numberStyle.Fg, cellColor, numberStyle.Modifier),
- im.Pt(x, (hm.Inner.Max.Y-2)-cellY),
- )
-}
-
-func color(colors [][]ui.Color, i, j int) ui.Color {
- return colors[i%len(colors)][j%len(colors)]
-}
-
-func style(styles [][]ui.Style, i, j int) ui.Style {
- return styles[i%len(styles)][j%len(styles)]
-}