Fix a bug and make several improvements (#51)
diff --git a/assets/templates/Dashboard.Global.json b/assets/templates/Dashboard.Global.json
index d82c108..318bde8 100644
--- a/assets/templates/Dashboard.Global.json
+++ b/assets/templates/Dashboard.Global.json
@@ -1,6 +1,5 @@
{
"buttons": {
- "texts": "Metrics, Response Latency",
"colorNumber": 220,
"height": 1
},
@@ -56,7 +55,8 @@
"normal": true
}
},
- "labels": "0, 1, 2, 3, 4",
+ "labels": "P50, P75, P90, P95, P99",
+ "labelsIndex": "0, 1, 2, 3, 4",
"title": "Global Response Latency",
"unit": "percentile in ms"
},
diff --git a/display/graph/dashboard/global.go b/display/graph/dashboard/global.go
index 445b541..c71f9af 100644
--- a/display/graph/dashboard/global.go
+++ b/display/graph/dashboard/global.go
@@ -19,6 +19,7 @@
import (
"context"
+ "fmt"
"math"
"strings"
@@ -46,13 +47,23 @@
type layoutType int
const (
- // layoutAll displays all the widgets.
- layoutAll layoutType = iota
+ // layoutMetrics displays all the widgets.
+ layoutMetrics layoutType = iota
// layoutLineChart focuses onto the line chart.
layoutLineChart
+
+ // layoutHeatMap focuses onto the heat map.
+ layoutHeatMap
)
+// strToLayoutType ensures the order of buttons is fixed.
+var strToLayoutType = map[string]layoutType{
+ "Metrics": layoutMetrics,
+ "ResponseLatency": layoutLineChart,
+ "HeatMap": layoutHeatMap,
+}
+
// widgets holds the widgets used by the dashboard.
type widgets struct {
gauges []*gauge.MetricColumn
@@ -62,6 +73,9 @@
buttons []*button.Button
}
+// linearTitles are titles of each line chart, load from the template file.
+var linearTitles []string
+
// setLayout sets the specified layout.
func setLayout(c *container.Container, w *widgets, lt layoutType) error {
gridOpts, err := gridLayout(w, lt)
@@ -75,17 +89,18 @@
func newLayoutButtons(c *container.Container, w *widgets, template *dashboard.ButtonTemplate) ([]*button.Button, error) {
var buttons []*button.Button
- buttonTexts := strings.Split(template.Texts, ",")
-
opts := []button.Option{
- button.WidthFor(longestString(buttonTexts)),
+ button.WidthFor(longestString(template.Texts)),
button.FillColor(cell.ColorNumber(template.ColorNum)),
button.Height(template.Height),
}
- for i, text := range buttonTexts {
+ for _, text := range template.Texts {
// declare a local variable lt to avoid closure.
- lt := layoutType(i)
+ lt, ok := strToLayoutType[text]
+ if !ok {
+ return nil, fmt.Errorf("the %s is not supposed to be the button's text", text)
+ }
b, err := button.New(text, func() error {
return setLayout(c, w, lt)
@@ -115,13 +130,13 @@
}
switch lt {
- case layoutAll:
+ case layoutMetrics:
rows = append(rows,
grid.RowHeightPerc(70, gauge.MetricColumnsElement(w.gauges)...),
)
case layoutLineChart:
- lcElements := linear.LineChartElements(w.linears)
+ lcElements := linear.LineChartElements(w.linears, linearTitles)
percentage := int(math.Min(99, float64((100-buttonRowHeight)/len(lcElements))))
for _, e := range lcElements {
@@ -191,6 +206,7 @@
if err != nil {
return err
}
+ linearTitles = strings.Split(template.ResponseLatency.Labels, ", ")
w, err := newWidgets(data, template)
if err != nil {
@@ -202,7 +218,7 @@
}
w.buttons = lb
- gridOpts, err := gridLayout(w, layoutAll)
+ gridOpts, err := gridLayout(w, layoutMetrics)
if err != nil {
return err
}
diff --git a/display/graph/linear/linear.go b/display/graph/linear/linear.go
index a437524..da219f4 100644
--- a/display/graph/linear/linear.go
+++ b/display/graph/linear/linear.go
@@ -66,7 +66,7 @@
// LineChartElements is the part that separated from layout,
// which can be reused by global dashboard.
-func LineChartElements(lineCharts []*linechart.LineChart) [][]grid.Element {
+func LineChartElements(lineCharts []*linechart.LineChart, titles []string) [][]grid.Element {
cols := maxSqrt(len(lineCharts))
rows := make([][]grid.Element, int(math.Ceil(float64(len(lineCharts))/float64(cols))))
@@ -78,13 +78,21 @@
if r == len(rows)-1 {
percentage = int(math.Floor(float64(100) / float64(len(lineCharts)-r*cols)))
}
+
+ var title string
+ if titles == nil {
+ title = fmt.Sprintf("#%v", r*cols+c)
+ } else {
+ title = titles[r*cols+c]
+ }
+
row = append(row, grid.ColWidthPerc(
int(math.Min(99, float64(percentage))),
grid.Widget(
lineCharts[r*cols+c],
container.Border(linestyle.Light),
container.BorderTitleAlignCenter(),
- container.BorderTitle(fmt.Sprintf("#%v", r*cols+c)),
+ container.BorderTitle(title),
),
))
}
@@ -130,7 +138,7 @@
elements = append(elements, w)
}
- gridOpts, err := layout(LineChartElements(elements))
+ gridOpts, err := layout(LineChartElements(elements, nil))
if err != nil {
return err
}
diff --git a/example/Dashboard.Global.json b/example/Dashboard.Global.json
index d82c108..318bde8 100644
--- a/example/Dashboard.Global.json
+++ b/example/Dashboard.Global.json
@@ -1,6 +1,5 @@
{
"buttons": {
- "texts": "Metrics, Response Latency",
"colorNumber": 220,
"height": 1
},
@@ -56,7 +55,8 @@
"normal": true
}
},
- "labels": "0, 1, 2, 3, 4",
+ "labels": "P50, P75, P90, P95, P99",
+ "labelsIndex": "0, 1, 2, 3, 4",
"title": "Global Response Latency",
"unit": "percentile in ms"
},
diff --git a/graphql/dashboard/global.go b/graphql/dashboard/global.go
index 39b4202..eccc266 100644
--- a/graphql/dashboard/global.go
+++ b/graphql/dashboard/global.go
@@ -35,9 +35,9 @@
)
type ButtonTemplate struct {
- Texts string `json:"texts"`
- ColorNum int `json:"colorNumber"`
- Height int `json:"height"`
+ Texts []string `json:"texts"`
+ ColorNum int `json:"colorNumber"`
+ Height int `json:"height"`
}
type MetricTemplate struct {
@@ -48,10 +48,11 @@
}
type ChartTemplate struct {
- Condition schema.MetricsCondition `json:"condition"`
- Title string `json:"title"`
- Unit string `json:"unit"`
- Labels string `json:"labels"`
+ Condition schema.MetricsCondition `json:"condition"`
+ Title string `json:"title"`
+ Unit string `json:"unit"`
+ Labels string `json:"labels"`
+ LabelsIndex string `json:"labelsIndex"`
}
type GlobalTemplate struct {
@@ -72,13 +73,23 @@
const DefaultTemplatePath = "templates/Dashboard.Global.json"
+// newGlobalTemplate create a new GlobalTemplate and set default values for buttons' template.
+func newGlobalTemplate() GlobalTemplate {
+ return GlobalTemplate{
+ Buttons: ButtonTemplate{
+ ColorNum: 220,
+ Height: 1,
+ },
+ }
+}
+
// LoadTemplate reads UI template from file.
func LoadTemplate(filename string) (*GlobalTemplate, error) {
if globalTemplate != nil {
return globalTemplate, nil
}
- var t GlobalTemplate
+ t := newGlobalTemplate()
var byteValue []byte
if filename == DefaultTemplatePath {
@@ -100,10 +111,31 @@
if err := json.Unmarshal(byteValue, &t); err != nil {
return nil, err
}
+ t.Buttons.Texts = getButtonTexts(byteValue)
+
globalTemplate = &t
return globalTemplate, nil
}
+// getButtonTexts get keys in the template file,
+// which will be set as texts of buttons in the dashboard.
+func getButtonTexts(byteValue []byte) []string {
+ var ret []string
+
+ c := make(map[string]json.RawMessage)
+ err := json.Unmarshal(byteValue, &c)
+ if err != nil {
+ return nil
+ }
+
+ for s := range c {
+ if s != "buttons" {
+ ret = append(ret, strings.Title(s))
+ }
+ }
+ return ret
+}
+
func Metrics(ctx *cli.Context, duration schema.Duration) [][]*schema.SelectedRecord {
var ret [][]*schema.SelectedRecord
@@ -112,6 +144,11 @@
return nil
}
+ // Check if there is a template of metrics.
+ if template.Metrics == nil {
+ return nil
+ }
+
for _, m := range template.Metrics {
var response map[string][]*schema.SelectedRecord
request := graphql.NewRequest(assets.Read("graphqls/dashboard/SortMetrics.graphql"))
@@ -133,14 +170,19 @@
return nil
}
- // labels in the template file is string type,
- // need to convert to string array for graphql query.
- labels := strings.Split(template.ResponseLatency.Labels, ",")
+ // Check if there is a template of response latency.
+ if template.ResponseLatency == (ChartTemplate{}) {
+ return nil
+ }
+
+ // LabelsIndex in the template file is string type, like "0, 1, 2",
+ // need use ", " to split into string array for graphql query.
+ labelsIndex := strings.Split(template.ResponseLatency.LabelsIndex, ", ")
request := graphql.NewRequest(assets.Read("graphqls/dashboard/LabeledMetricsValues.graphql"))
request.Var("duration", duration)
request.Var("condition", template.ResponseLatency.Condition)
- request.Var("labels", labels)
+ request.Var("labels", labelsIndex)
client.ExecuteQueryOrFail(ctx, request, &response)
@@ -167,6 +209,11 @@
return schema.HeatMap{}
}
+ // Check if there is a template of heat map.
+ if template.HeatMap == (ChartTemplate{}) {
+ return schema.HeatMap{}
+ }
+
request := graphql.NewRequest(assets.Read("graphqls/dashboard/HeatMap.graphql"))
request.Var("duration", duration)
request.Var("condition", template.HeatMap.Condition)