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/08 06:54:56 UTC
[skywalking-cli] 01/01: [Feature] Support visualization of heat map
and enhance Display logic
This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch feature/thermodynamic
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git
commit fadc3a47f263389de7b80e0f3474ec972ddc42ee
Author: kezhenxu94 <ke...@163.com>
AuthorDate: Sun Mar 8 14:54:30 2020 +0800
[Feature] Support visualization of heat map and enhance Display logic
### Motivation
Visualize heat map and enhance Display logic
### Modification
- Add HeatMap widget to visualize thermodynamic metrics.
- Refactor display logic.
### Result
- Thermodynamic metrics now can be visualized.
- Display function takes more extra information to determine more details
- Closes https://github.com/apache/skywalking/issues/4461
---
README.md | 8 +-
commands/endpoint/list.go | 4 +-
commands/instance/list.go | 4 +-
commands/instance/search.go | 4 +-
commands/metrics/aggregation/topn.go | 4 +-
commands/metrics/linear/linear-metrics.go | 4 +-
commands/metrics/linear/multiple-linear-metrics.go | 4 +-
commands/metrics/single/single-metrics.go | 4 +-
commands/metrics/thermodynamic/thermodynamic.go | 8 +-
commands/service/list.go | 4 +-
display/display.go | 12 ++-
.../{json/json.go => displayable/displayable.go} | 19 ++--
display/graph/graph.go | 22 ++--
display/graph/heatmap/heatmap.go | 114 +++++++++++++++++++++
display/json/json.go | 13 +--
display/json/json_test.go | 4 +-
display/table/table.go | 6 +-
display/table/table_test.go | 4 +-
display/yaml/yaml.go | 13 +--
display/yaml/yaml_test.go | 4 +-
dist/LICENSE | 2 +
dist/licenses/LICENSE-go-runewidth | 21 ++++
dist/licenses/LICENSE-termui | 21 ++++
go.mod | 4 +-
go.sum | 7 ++
graphql/metrics/metrics.go | 2 +-
lib/heatmap.go | 114 +++++++++++++++++++++
display/json/json.go => util/math.go | 18 +---
28 files changed, 381 insertions(+), 67 deletions(-)
diff --git a/README.md b/README.md
index 9fba39b..7e748b7 100644
--- a/README.md
+++ b/README.md
@@ -461,13 +461,17 @@ $ ./bin/swctl metrics top 5 --name endpoint_sla --service-id 3
<details>
-<summary>Query the overall heatmap</summary>
+<summary>Query the overall heat map</summary>
```shell
-$ ./bin/swctl metrics thermodynamic --name all_heatmap
+$ ./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 [...]
```
+```shell
+$ ./bin/swctl --display=graph metrics thermodynamic --name all_heatmap
+```
+
</details>
<details>
diff --git a/commands/endpoint/list.go b/commands/endpoint/list.go
index 034c6a5..3c6767b 100644
--- a/commands/endpoint/list.go
+++ b/commands/endpoint/list.go
@@ -20,6 +20,8 @@ package endpoint
import (
"github.com/urfave/cli"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metadata"
"github.com/apache/skywalking-cli/display"
@@ -56,6 +58,6 @@ var ListCommand = cli.Command{
endpoints := metadata.SearchEndpoints(ctx, serviceID, keyword, limit)
- return display.Display(ctx, endpoints)
+ return display.Display(ctx, &displayable.Displayable{Data: endpoints})
},
}
diff --git a/commands/instance/list.go b/commands/instance/list.go
index eda72c1..1cfcd88 100644
--- a/commands/instance/list.go
+++ b/commands/instance/list.go
@@ -20,6 +20,8 @@ package instance
import (
"github.com/urfave/cli"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metadata"
"github.com/apache/skywalking-cli/commands/flags"
@@ -51,6 +53,6 @@ var ListCommand = cli.Command{
Step: step.(*model.StepEnumValue).Selected,
})
- return display.Display(ctx, instances)
+ return display.Display(ctx, &displayable.Displayable{Data: instances})
},
}
diff --git a/commands/instance/search.go b/commands/instance/search.go
index b97adae..548606f 100644
--- a/commands/instance/search.go
+++ b/commands/instance/search.go
@@ -20,6 +20,8 @@ package instance
import (
"regexp"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metadata"
"github.com/urfave/cli"
@@ -62,6 +64,6 @@ var SearchCommand = cli.Command{
}
}
}
- return display.Display(ctx, result)
+ return display.Display(ctx, &displayable.Displayable{Data: result})
},
}
diff --git a/commands/metrics/aggregation/topn.go b/commands/metrics/aggregation/topn.go
index 77876f6..ce6c63d 100644
--- a/commands/metrics/aggregation/topn.go
+++ b/commands/metrics/aggregation/topn.go
@@ -22,6 +22,8 @@ import (
"strconv"
"strings"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/commands/interceptor"
"github.com/urfave/cli"
@@ -107,6 +109,6 @@ var TopN = cli.Command{
metricsValues = aggregation.ServiceTopN(ctx, name, topN, duration, order)
}
- return display.Display(ctx, metricsValues)
+ return display.Display(ctx, &displayable.Displayable{Data: metricsValues})
},
}
diff --git a/commands/metrics/linear/linear-metrics.go b/commands/metrics/linear/linear-metrics.go
index d435063..c1aa19b 100644
--- a/commands/metrics/linear/linear-metrics.go
+++ b/commands/metrics/linear/linear-metrics.go
@@ -20,6 +20,8 @@ package linear
import (
"github.com/urfave/cli"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metrics"
"github.com/apache/skywalking-cli/graphql/utils"
@@ -75,6 +77,6 @@ var Single = cli.Command{
ID: id,
}, duration)
- return display.Display(ctx, utils.MetricsToMap(duration, metricsValues))
+ return display.Display(ctx, &displayable.Displayable{Data: utils.MetricsToMap(duration, metricsValues)})
},
}
diff --git a/commands/metrics/linear/multiple-linear-metrics.go b/commands/metrics/linear/multiple-linear-metrics.go
index 31db251..fae3580 100644
--- a/commands/metrics/linear/multiple-linear-metrics.go
+++ b/commands/metrics/linear/multiple-linear-metrics.go
@@ -20,6 +20,8 @@ package linear
import (
"github.com/urfave/cli"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metrics"
"github.com/apache/skywalking-cli/graphql/utils"
@@ -88,6 +90,6 @@ var Multiple = cli.Command{
reshaped[index] = utils.MetricsToMap(duration, value)
}
- return display.Display(ctx, reshaped)
+ return display.Display(ctx, &displayable.Displayable{Data: reshaped})
},
}
diff --git a/commands/metrics/single/single-metrics.go b/commands/metrics/single/single-metrics.go
index 69405ea..1f710c0 100644
--- a/commands/metrics/single/single-metrics.go
+++ b/commands/metrics/single/single-metrics.go
@@ -20,6 +20,8 @@ package single
import (
"strings"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metrics"
"github.com/urfave/cli"
@@ -75,6 +77,6 @@ var Command = cli.Command{
Step: step.(*model.StepEnumValue).Selected,
})
- return display.Display(ctx, metricsValues.Values)
+ return display.Display(ctx, &displayable.Displayable{Data: metricsValues.Values})
},
}
diff --git a/commands/metrics/thermodynamic/thermodynamic.go b/commands/metrics/thermodynamic/thermodynamic.go
index 5eefb3a..05c4ba4 100644
--- a/commands/metrics/thermodynamic/thermodynamic.go
+++ b/commands/metrics/thermodynamic/thermodynamic.go
@@ -20,6 +20,8 @@ package thermodynamic
import (
"github.com/urfave/cli"
+ "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/commands/model"
@@ -62,6 +64,10 @@ var Command = cli.Command{
Name: metricsName,
}, duration)
- return display.Display(ctx, metricsValues)
+ return display.Display(ctx, &displayable.Displayable{
+ Data: metricsValues,
+ Duration: duration,
+ Title: metricsName,
+ })
},
}
diff --git a/commands/service/list.go b/commands/service/list.go
index 2604a6f..14e2830 100644
--- a/commands/service/list.go
+++ b/commands/service/list.go
@@ -20,6 +20,8 @@ package service
import (
"github.com/urfave/cli"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/metadata"
"github.com/apache/skywalking-cli/commands/flags"
@@ -58,6 +60,6 @@ var ListCommand = cli.Command{
services = []schema.Service{service}
}
- return display.Display(ctx, services)
+ return display.Display(ctx, &displayable.Displayable{Data: services})
},
}
diff --git a/display/display.go b/display/display.go
index e620673..af3a598 100644
--- a/display/display.go
+++ b/display/display.go
@@ -21,6 +21,8 @@ import (
"fmt"
"strings"
+ d "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/display/graph"
"github.com/urfave/cli"
@@ -38,18 +40,18 @@ const (
)
// Display the object in the style specified in flag --display
-func Display(ctx *cli.Context, object interface{}) error {
+func Display(ctx *cli.Context, displayable *d.Displayable) error {
displayStyle := ctx.GlobalString("display")
switch strings.ToLower(displayStyle) {
case JSON:
- return json.Display(object)
+ return json.Display(displayable)
case YAML:
- return yaml.Display(object)
+ return yaml.Display(displayable)
case TABLE:
- return table.Display(object)
+ return table.Display(displayable)
case GRAPH:
- return graph.Display(object)
+ return graph.Display(displayable)
default:
return fmt.Errorf("unsupported display style: %s", displayStyle)
}
diff --git a/display/json/json.go b/display/displayable/displayable.go
similarity index 79%
copy from display/json/json.go
copy to display/displayable/displayable.go
index 25aaccf..12ca7d2 100644
--- a/display/json/json.go
+++ b/display/displayable/displayable.go
@@ -15,19 +15,12 @@
// specific language governing permissions and limitations
// under the License.
-package json
+package displayable
-import (
- "encoding/json"
- "fmt"
-)
+import "github.com/apache/skywalking-cli/graphql/schema"
-func Display(object interface{}) error {
- if bytes, e := json.Marshal(object); e == nil {
- fmt.Printf("%v\n", string(bytes))
- } else {
- return e
- }
-
- return nil
+type Displayable struct {
+ Data interface{}
+ Duration schema.Duration
+ Title string
}
diff --git a/display/graph/graph.go b/display/graph/graph.go
index c27f835..e8fc072 100644
--- a/display/graph/graph.go
+++ b/display/graph/graph.go
@@ -21,21 +21,31 @@ import (
"fmt"
"reflect"
+ "github.com/apache/skywalking-cli/display/graph/heatmap"
+ "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(object interface{}) error {
- if reflect.TypeOf(object) == reflect.TypeOf(map[string]float64{}) {
- kvs := []map[string]float64{object.(map[string]float64)}
+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(object) == reflect.TypeOf([]map[string]float64{}) {
- kvs := object.([]map[string]float64)
+ 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(object))
+ return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data))
}
diff --git a/display/graph/heatmap/heatmap.go b/display/graph/heatmap/heatmap.go
new file mode 100644
index 0000000..0ca653a
--- /dev/null
+++ b/display/graph/heatmap/heatmap.go
@@ -0,0 +1,114 @@
+// 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 heatmap
+
+import (
+ "fmt"
+ "math"
+ "time"
+
+ "github.com/apache/skywalking-cli/graphql/utils"
+ "github.com/apache/skywalking-cli/util"
+
+ ui "github.com/gizak/termui/v3"
+
+ d "github.com/apache/skywalking-cli/display/displayable"
+ "github.com/apache/skywalking-cli/graphql/schema"
+ "github.com/apache/skywalking-cli/lib"
+)
+
+func Display(displayable *d.Displayable) error {
+ data := displayable.Data.(schema.Thermodynamic)
+
+ nodes := data.Nodes
+ duration := displayable.Duration
+
+ rows, cols, min, max := statistics(nodes)
+
+ if err := ui.Init(); err != nil {
+ return err
+ }
+ defer ui.Close()
+
+ termW, _ := ui.TerminalDimensions()
+
+ 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)
+
+ if err != nil {
+ return err
+ }
+
+ hm.XLabels[i] = startTime.Add(time.Duration(i) * step).Format("15:04")
+ }
+ for i := 0; i < cols; i++ {
+ hm.YLabels[i] = fmt.Sprintf("%4d", i*data.AxisYStep)
+ }
+
+ 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)
+ }
+
+ 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}
+ }
+
+ 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 {
+ }
+ 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]))
+ }
+
+ rows++
+ cols++
+ return
+}
diff --git a/display/json/json.go b/display/json/json.go
index 25aaccf..12a506f 100644
--- a/display/json/json.go
+++ b/display/json/json.go
@@ -20,14 +20,15 @@ package json
import (
"encoding/json"
"fmt"
+
+ d "github.com/apache/skywalking-cli/display/displayable"
)
-func Display(object interface{}) error {
- if bytes, e := json.Marshal(object); e == nil {
- fmt.Printf("%v\n", string(bytes))
- } else {
+func Display(displayable *d.Displayable) error {
+ bytes, e := json.Marshal(displayable.Data)
+ if e != nil {
return e
}
-
- return nil
+ _, e = fmt.Printf("%v\n", string(bytes))
+ return e
}
diff --git a/display/json/json_test.go b/display/json/json_test.go
index 8e30107..9d8ead4 100644
--- a/display/json/json_test.go
+++ b/display/json/json_test.go
@@ -20,6 +20,8 @@ package json
import (
"testing"
+ d "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/schema"
)
@@ -36,7 +38,7 @@ func TestJsonDisplay(t *testing.T) {
}
func display(t *testing.T, result []schema.Service) {
- if err := Display(result); err != nil {
+ if err := Display(&d.Displayable{Data: result}); err != nil {
t.Error(err)
}
}
diff --git a/display/table/table.go b/display/table/table.go
index ce2a5ea..8e2227d 100644
--- a/display/table/table.go
+++ b/display/table/table.go
@@ -21,15 +21,17 @@ import (
"encoding/json"
"os"
+ d "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/logger"
"github.com/olekukonko/tablewriter"
)
-func Display(object interface{}) error {
+func Display(displayable *d.Displayable) error {
var stringMapArrays []map[string]string
- bytes, _ := json.Marshal(object)
+ bytes, _ := json.Marshal(displayable.Data)
_ = json.Unmarshal(bytes, &stringMapArrays)
if len(stringMapArrays) < 1 {
diff --git a/display/table/table_test.go b/display/table/table_test.go
index fd24460..ec2b265 100644
--- a/display/table/table_test.go
+++ b/display/table/table_test.go
@@ -20,6 +20,8 @@ package table
import (
"testing"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/schema"
)
@@ -36,7 +38,7 @@ func TestTableDisplay(t *testing.T) {
}
func display(t *testing.T, result []schema.Service) {
- if err := Display(result); err != nil {
+ if err := Display(&displayable.Displayable{Data: result}); err != nil {
t.Error(err)
}
}
diff --git a/display/yaml/yaml.go b/display/yaml/yaml.go
index bb8cb79..3f3c98b 100644
--- a/display/yaml/yaml.go
+++ b/display/yaml/yaml.go
@@ -20,15 +20,16 @@ package yaml
import (
"fmt"
+ d "github.com/apache/skywalking-cli/display/displayable"
+
"gopkg.in/yaml.v2"
)
-func Display(object interface{}) error {
- if bytes, e := yaml.Marshal(object); e == nil {
- fmt.Printf("%v", string(bytes))
- } else {
+func Display(displayable *d.Displayable) error {
+ bytes, e := yaml.Marshal(displayable.Data)
+ if e != nil {
return e
}
-
- return nil
+ _, e = fmt.Printf("%v", string(bytes))
+ return e
}
diff --git a/display/yaml/yaml_test.go b/display/yaml/yaml_test.go
index 81a772b..aaada3a 100644
--- a/display/yaml/yaml_test.go
+++ b/display/yaml/yaml_test.go
@@ -20,6 +20,8 @@ package yaml
import (
"testing"
+ "github.com/apache/skywalking-cli/display/displayable"
+
"github.com/apache/skywalking-cli/graphql/schema"
)
@@ -36,7 +38,7 @@ func TestYamlDisplay(t *testing.T) {
}
func display(t *testing.T, result []schema.Service) {
- if err := Display(result); err != nil {
+ if err := Display(&displayable.Displayable{Data: result}); err != nil {
t.Error(err)
}
}
diff --git a/dist/LICENSE b/dist/LICENSE
index e5b6732..4dfc4c4 100644
--- a/dist/LICENSE
+++ b/dist/LICENSE
@@ -222,6 +222,8 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
sirupsen (logrus) 1.4.2: https://github.com/sirupsen/logrus MIT
urfave (cli) 1.22.1: https://github.com/urfave/cli MIT
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
========================================================================
BSD licenses
diff --git a/dist/licenses/LICENSE-go-runewidth b/dist/licenses/LICENSE-go-runewidth
new file mode 100644
index 0000000..91b5cef
--- /dev/null
+++ b/dist/licenses/LICENSE-go-runewidth
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Yasuhiro Matsumoto
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/dist/licenses/LICENSE-termui b/dist/licenses/LICENSE-termui
new file mode 100644
index 0000000..b8beeb7
--- /dev/null
+++ b/dist/licenses/LICENSE-termui
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Zack Guo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/go.mod b/go.mod
index f28ffcc..7586d33 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,11 @@ go 1.13
require (
github.com/99designs/gqlgen v0.11.1 // indirect
+ github.com/gizak/termui/v3 v3.1.0
github.com/machinebox/graphql v0.2.2
+ github.com/mattn/go-runewidth v0.0.4
github.com/mum4k/termdash v0.10.0
- github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 // indirect
+ github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317
github.com/olekukonko/tablewriter v0.0.2
github.com/sirupsen/logrus v1.4.2
github.com/urfave/cli v1.22.1
diff --git a/go.sum b/go.sum
index cd718b5..932479d 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
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/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+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/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -34,12 +37,16 @@ github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIG
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=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+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/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/mum4k/termdash v0.10.0 h1:uqM6ePiMf+smecb1tJJeON36o1hREeCfOmLFG0iz4a0=
github.com/mum4k/termdash v0.10.0/go.mod h1:l3tO+lJi9LZqXRq7cu7h5/8rDIK3AzelSuq2v/KncxI=
+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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
diff --git a/graphql/metrics/metrics.go b/graphql/metrics/metrics.go
index 68cf36d..ef71e32 100644
--- a/graphql/metrics/metrics.go
+++ b/graphql/metrics/metrics.go
@@ -85,7 +85,7 @@ func Thermodynamic(ctx *cli.Context, condition schema.MetricCondition, duration
request := graphql.NewRequest(`
query ($metric: MetricCondition!, $duration: Duration!) {
metrics: getThermodynamic(metric: $metric, duration: $duration) {
- nodes responseTimeStep: axisYStep
+ nodes axisYStep
}
}
`)
diff --git a/lib/heatmap.go b/lib/heatmap.go
new file mode 100644
index 0000000..bf24b86
--- /dev/null
+++ b/lib/heatmap.go
@@ -0,0 +1,114 @@
+// 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)]
+}
diff --git a/display/json/json.go b/util/math.go
similarity index 79%
copy from display/json/json.go
copy to util/math.go
index 25aaccf..ddbe2a8 100644
--- a/display/json/json.go
+++ b/util/math.go
@@ -15,19 +15,11 @@
// specific language governing permissions and limitations
// under the License.
-package json
+package util
-import (
- "encoding/json"
- "fmt"
-)
-
-func Display(object interface{}) error {
- if bytes, e := json.Marshal(object); e == nil {
- fmt.Printf("%v\n", string(bytes))
- } else {
- return e
+func MaxInt(a, b int) int {
+ if a > b {
+ return a
}
-
- return nil
+ return b
}