From dc53c7d78bd1b06c1658304029768cef10657ed1 Mon Sep 17 00:00:00 2001 From: kezhenxu94 Date: Wed, 13 Oct 2021 16:55:45 +0800 Subject: [PATCH] fix: `multiple-linear` command's `labels` type can be string type --- assets/templates/dashboard/global.yml | 6 +-- examples/global.yml | 6 +-- .../metrics/linear/multiple-linear-metrics.go | 38 ++++++++++++++++--- pkg/display/graph/dashboard/global.go | 14 +++---- pkg/display/graph/graph.go | 10 ++--- pkg/display/graph/linear/linear.go | 32 +++++++++------- pkg/graphql/dashboard/global.go | 34 ++++++++++------- pkg/graphql/utils/adapter.go | 15 +++----- test/expected/dashboard-global.yml | 5 ++- 9 files changed, 95 insertions(+), 65 deletions(-) diff --git a/assets/templates/dashboard/global.yml b/assets/templates/dashboard/global.yml index b9377a7e..9b7d9f76 100644 --- a/assets/templates/dashboard/global.yml +++ b/assets/templates/dashboard/global.yml @@ -76,8 +76,8 @@ responseLatency: entity: scope: "All" normal: true - labels: "P50, P75, P90, P95, P99" - labelsIndex: "0, 1, 2, 3, 4" + relabels: "P50,P75,P90,P95,P99" + labels: "0,1,2,3,4" title: "Global Response Latency" unit: "percentile in ms" @@ -89,4 +89,4 @@ heatMap: scope: "All" normal: true title: "Global Heatmap" - unit: "ms" \ No newline at end of file + unit: "ms" diff --git a/examples/global.yml b/examples/global.yml index b9377a7e..33e8615d 100644 --- a/examples/global.yml +++ b/examples/global.yml @@ -76,8 +76,8 @@ responseLatency: entity: scope: "All" normal: true - labels: "P50, P75, P90, P95, P99" - labelsIndex: "0, 1, 2, 3, 4" + relabels: "P50, P75, P90, P95, P99" + labels: "0,1,2,3,4" title: "Global Response Latency" unit: "percentile in ms" @@ -89,4 +89,4 @@ heatMap: scope: "All" normal: true title: "Global Heatmap" - unit: "ms" \ No newline at end of file + unit: "ms" diff --git a/internal/commands/metrics/linear/multiple-linear-metrics.go b/internal/commands/metrics/linear/multiple-linear-metrics.go index af4a4078..5e987e0a 100644 --- a/internal/commands/metrics/linear/multiple-linear-metrics.go +++ b/internal/commands/metrics/linear/multiple-linear-metrics.go @@ -41,7 +41,11 @@ var Multiple = &cli.Command{ Examples: 1. Query the global percentiles: -$ swctl metrics multiple-linear --name all_percentile`, +$ swctl metrics multiple-linear --name all_percentile + +2. Relabel the labels for better readability: +$ swctl metrics multiple-linear --name all_percentile --labels=0,1,2,3,4 --relabels=P50,P75,P90,P95,P99 +`, Flags: flags.Flags( flags.DurationFlags, flags.MetricsFlags, @@ -52,7 +56,14 @@ $ swctl metrics multiple-linear --name all_percentile`, Name: "labels", Usage: "the labels you need to query", Required: false, - Value: "0,1,2,3,4", + }, + }, + []cli.Flag{ + &cli.StringFlag{ + Name: "relabels", + Usage: `the new labels to map to the original "--labels", must be in same size and is order-sensitive. ` + + `"labels[i]" will be mapped to "relabels[i]"`, + Required: false, }, }, ), @@ -67,7 +78,24 @@ $ swctl metrics multiple-linear --name all_percentile`, step := ctx.Generic("step") metricsName := ctx.String("name") - labels := ctx.String("labels") + labelsString := ctx.String("labels") + relabelsString := ctx.String("relabels") + + labels := strings.Split(labelsString, ",") + relabels := strings.Split(relabelsString, ",") + + labelMapping := make(map[string]string) + switch { + case labelsString == "" && relabelsString != "": + return fmt.Errorf(`"--labels" cannot be empty when "--relabels" is given`) + case labelsString != "" && relabelsString != "" && len(labels) != len(relabels): + return fmt.Errorf(`"--labels" and "--relabels" must be in same size if both specified, but was %v != %v`, len(labels), len(relabels)) + case relabelsString != "": + for i := 0; i < len(labels); i++ { + labelMapping[labels[i]] = relabels[i] + } + } + entity, err := interceptor.ParseEntity(ctx) if err != nil { return err @@ -86,13 +114,13 @@ $ swctl metrics multiple-linear --name all_percentile`, metricsValuesArray, err := metrics.MultipleLinearIntValues(ctx, api.MetricsCondition{ Name: metricsName, Entity: entity, - }, strings.Split(labels, ","), duration) + }, labels, duration) if err != nil { return err } - reshaped := utils.MetricsValuesArrayToMap(duration, metricsValuesArray) + reshaped := utils.MetricsValuesArrayToMap(duration, metricsValuesArray, labelMapping) return display.Display(ctx, &displayable.Displayable{Data: reshaped}) }, } diff --git a/pkg/display/graph/dashboard/global.go b/pkg/display/graph/dashboard/global.go index 97c1801e..52a01c3d 100644 --- a/pkg/display/graph/dashboard/global.go +++ b/pkg/display/graph/dashboard/global.go @@ -75,16 +75,13 @@ var strToLayoutType = map[string]layoutType{ // widgets holds the widgets used by the dashboard. type widgets struct { gauges []*gauge.MetricColumn - linears []*linechart.LineChart + linears map[string]*linechart.LineChart heatmap *lib.HeatMap // buttons are used to change the layout. buttons []*button.Button } -// linearTitles are titles of each line chart, load from the template file. -var linearTitles []string - // template determines how the global dashboard is displayed. var template *dashboard.GlobalTemplate @@ -164,7 +161,7 @@ func gridLayout(lt layoutType) ([]container.Option, error) { ) case layoutLineChart: - lcElements := linear.LineChartElements(allWidgets.linears, linearTitles) + lcElements := linear.LineChartElements(allWidgets.linears) percentage := int(math.Min(99, float64((100-buttonRowHeight)/len(lcElements)))) for _, e := range lcElements { @@ -200,7 +197,7 @@ func gridLayout(lt layoutType) ([]container.Option, error) { // newWidgets creates all widgets used by the dashboard. func newWidgets(data *dashboard.GlobalData) error { var columns []*gauge.MetricColumn - var linears []*linechart.LineChart + linears := make(map[string]*linechart.LineChart) // Create gauges to display global metrics. for i := range template.Metrics { @@ -212,12 +209,12 @@ func newWidgets(data *dashboard.GlobalData) error { } // Create line charts to display global response latency. - for _, input := range data.ResponseLatency { + for label, input := range data.ResponseLatency { l, err := linear.NewLineChart(input) if err != nil { return err } - linears = append(linears, l) + linears[label] = l } // Create a heat map. @@ -253,7 +250,6 @@ func Display(ctx *cli.Context, data *dashboard.GlobalData) error { return err } template = te - linearTitles = strings.Split(template.ResponseLatency.Labels, ", ") // Initialization allWidgets = &widgets{ diff --git a/pkg/display/graph/graph.go b/pkg/display/graph/graph.go index 83340486..4f9db130 100644 --- a/pkg/display/graph/graph.go +++ b/pkg/display/graph/graph.go @@ -20,7 +20,6 @@ package graph import ( "fmt" "reflect" - "strings" api "skywalking.apache.org/repo/goapi/query" @@ -38,7 +37,7 @@ import ( type ( Thermodynamic = api.HeatMap LinearMetrics = map[string]float64 - MultiLinearMetrics = []LinearMetrics + MultiLinearMetrics = map[string]LinearMetrics Trace = api.Trace TraceBrief = api.TraceBrief GlobalMetrics = [][]*api.SelectedRecord @@ -55,8 +54,6 @@ var ( GlobalDataType = reflect.TypeOf(&GlobalData{}) ) -const multipleLinearTitles = "P50, P75, P90, P95, P99" - func Display(ctx *cli.Context, displayable *d.Displayable) error { data := displayable.Data @@ -65,12 +62,11 @@ func Display(ctx *cli.Context, displayable *d.Displayable) error { return heatmap.Display(displayable) case LinearMetricsType: - return linear.Display(ctx, []LinearMetrics{data.(LinearMetrics)}, nil) + return linear.Display(ctx, map[string]LinearMetrics{"": data.(LinearMetrics)}) case MultiLinearMetricsType: inputs := data.(MultiLinearMetrics) - titles := strings.Split(multipleLinearTitles, ", ")[:len(inputs)] - return linear.Display(ctx, inputs, titles) + return linear.Display(ctx, inputs) case TraceType: return tree.Display(tree.Adapt(data.(Trace))) diff --git a/pkg/display/graph/linear/linear.go b/pkg/display/graph/linear/linear.go index 5dc443f2..0186b4db 100644 --- a/pkg/display/graph/linear/linear.go +++ b/pkg/display/graph/linear/linear.go @@ -78,11 +78,21 @@ func processInputs(inputs map[string]float64) (xLabels map[int]string, yValues [ // LineChartElements is the part that separated from layout, // which can be reused by global dashboard. -func LineChartElements(lineCharts []*linechart.LineChart, titles []string) [][]grid.Element { +func LineChartElements(lineCharts map[string]*linechart.LineChart) [][]grid.Element { cols := maxSqrt(len(lineCharts)) rows := make([][]grid.Element, int(math.Ceil(float64(len(lineCharts))/float64(cols)))) + var charts []*linechart.LineChart + var titles []string + for t := range lineCharts { + titles = append(titles, t) + } + sort.Strings(titles) + for _, title := range titles { + charts = append(charts, lineCharts[title]) + } + for r := 0; r < len(rows); r++ { var row []grid.Element for c := 0; c < cols && r*cols+c < len(lineCharts); c++ { @@ -91,17 +101,13 @@ func LineChartElements(lineCharts []*linechart.LineChart, titles []string) [][]g 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] - } + title := titles[r*cols+c] + chart := charts[r*cols+c] row = append(row, grid.ColWidthPerc( int(math.Min(99, float64(percentage))), grid.Widget( - lineCharts[r*cols+c], + chart, container.Border(linestyle.Light), container.BorderTitleAlignCenter(), container.BorderTitle(title), @@ -125,7 +131,7 @@ func layout(rows [][]grid.Element) ([]container.Option, error) { return builder.Build() } -func Display(cliCtx *cli.Context, inputs []map[string]float64, titles []string) error { +func Display(cliCtx *cli.Context, inputs map[string]map[string]float64) error { t, err := termbox.New() if err != nil { return err @@ -140,17 +146,17 @@ func Display(cliCtx *cli.Context, inputs []map[string]float64, titles []string) return err } - var elements []*linechart.LineChart + elements := make(map[string]*linechart.LineChart) - for _, input := range inputs { + for title, input := range inputs { w, e := NewLineChart(input) if e != nil { return e } - elements = append(elements, w) + elements[title] = w } - gridOpts, err := layout(LineChartElements(elements, titles)) + gridOpts, err := layout(LineChartElements(elements)) if err != nil { return err } diff --git a/pkg/graphql/dashboard/global.go b/pkg/graphql/dashboard/global.go index eb0b2fcf..3586de4e 100644 --- a/pkg/graphql/dashboard/global.go +++ b/pkg/graphql/dashboard/global.go @@ -50,11 +50,11 @@ type MetricTemplate struct { } type ChartTemplate struct { - Condition api.MetricsCondition `mapstructure:"condition"` - Title string `mapstructure:"title"` - Unit string `mapstructure:"unit"` - Labels string `mapstructure:"labels"` - LabelsIndex string `mapstructure:"labelsIndex"` + Condition api.MetricsCondition `mapstructure:"condition"` + Title string `mapstructure:"title"` + Unit string `mapstructure:"unit"` + Relabels string `mapstructure:"relabels"` + Labels string `mapstructure:"labels"` } type GlobalTemplate struct { @@ -65,9 +65,9 @@ type GlobalTemplate struct { } type GlobalData struct { - Metrics [][]*api.SelectedRecord `json:"metrics"` - ResponseLatency []map[string]float64 `json:"responseLatency"` - HeatMap api.HeatMap `json:"heatMap"` + Metrics [][]*api.SelectedRecord `json:"metrics"` + ResponseLatency map[string]map[string]float64 `json:"responseLatency"` + HeatMap api.HeatMap `json:"heatMap"` } // Use singleton pattern to make sure to load template only once. @@ -164,7 +164,7 @@ func Metrics(ctx *cli.Context, duration api.Duration) ([][]*api.SelectedRecord, return ret, nil } -func responseLatency(ctx *cli.Context, duration api.Duration) []map[string]float64 { +func responseLatency(ctx *cli.Context, duration api.Duration) map[string]map[string]float64 { template, err := LoadTemplate(ctx.String("template")) if err != nil { return nil @@ -175,18 +175,24 @@ func responseLatency(ctx *cli.Context, duration api.Duration) []map[string]float return nil } - // LabelsIndex in the template file is string type, like "0, 1, 2", + // Labels 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, ", ") + labels := strings.Split(template.ResponseLatency.Labels, ",") + relabels := strings.Split(template.ResponseLatency.Relabels, ",") - responseLatency, err := metrics.MultipleLinearIntValues(ctx, template.ResponseLatency.Condition, labelsIndex, duration) + responseLatency, err := metrics.MultipleLinearIntValues(ctx, template.ResponseLatency.Condition, labels, duration) if err != nil { logger.Log.Fatalln(err) } + mapping := make(map[string]string, len(labels)) + for i := 0; i < len(labels); i++ { + mapping[labels[i]] = relabels[i] + } + // Convert metrics values to map type data. - return utils.MetricsValuesArrayToMap(duration, responseLatency) + return utils.MetricsValuesArrayToMap(duration, responseLatency, mapping) } func heatMap(ctx *cli.Context, duration api.Duration) (api.HeatMap, error) { @@ -224,7 +230,7 @@ func Global(ctx *cli.Context, duration api.Duration) (*GlobalData, error) { } wg.Done() }() - var rl []map[string]float64 + var rl map[string]map[string]float64 go func() { rl = responseLatency(ctx, duration) wg.Done() diff --git a/pkg/graphql/utils/adapter.go b/pkg/graphql/utils/adapter.go index 129d9024..f8af4627 100644 --- a/pkg/graphql/utils/adapter.go +++ b/pkg/graphql/utils/adapter.go @@ -18,8 +18,6 @@ package utils import ( - "strconv" - "strings" "time" api "skywalking.apache.org/repo/goapi/query" @@ -28,15 +26,14 @@ import ( ) // MetricsValuesArrayToMap converts Array of MetricsValues into a map that uses time as key. -func MetricsValuesArrayToMap(duration api.Duration, mvArray []api.MetricsValues) []map[string]float64 { - ret := make([]map[string]float64, len(mvArray)) +func MetricsValuesArrayToMap(duration api.Duration, mvArray []api.MetricsValues, labelsMap map[string]string) map[string]map[string]float64 { + ret := make(map[string]map[string]float64, len(mvArray)) for _, mvs := range mvArray { - index, err := strconv.Atoi(strings.TrimSpace(*mvs.Label)) - if err != nil { - logger.Log.Fatalln(err) - return nil + label := *mvs.Label + if l, ok := labelsMap[label]; ok { + label = l } - ret[index] = MetricsValuesToMap(duration, mvs) + ret[label] = MetricsValuesToMap(duration, mvs) } return ret } diff --git a/test/expected/dashboard-global.yml b/test/expected/dashboard-global.yml index 5c51e061..6d57aeec 100644 --- a/test/expected/dashboard-global.yml +++ b/test/expected/dashboard-global.yml @@ -28,8 +28,9 @@ metrics: {{- end }} responselatency: - {{- range .responselatency }} - - {{- range $k, $v := . }} + {{- range $k1, $v1 := .responselatency }} + {{ $k1 }}: + {{- range $k, $v := $v1 }} {{ $k }}: {{ $v }} {{- end }} {{- end }}