You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2019/11/26 09:26:03 UTC

[skywalking-cli] branch master updated: [Feature] Add metrics commands and support display in AsciiGraph style (#14)

This is an automated email from the ASF dual-hosted git repository.

wusheng 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 6e0db0a  [Feature] Add metrics commands and support display in AsciiGraph style (#14)
6e0db0a is described below

commit 6e0db0aae85034eea1667456bb0ac2ff99b55454
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Tue Nov 26 17:25:56 2019 +0800

    [Feature] Add metrics commands and support display in AsciiGraph style (#14)
    
    * Add metrics commands and support display in AsciiGraph style
    
    * Refactor to avoid hard-coded metrics name
    
    * Polish and lint codes
    
    * Add documentation
    
    * Rename metrics command to linear-metrics
    
    * Polish documentation
    
    * Rename
---
 README.md                                         |  10 ++
 display/display.go => commands/flags/flags.go     |  38 ++-----
 commands/interceptor/duration.go                  |  26 +----
 commands/metrics/linear-metrics.go                |  63 ++++++++++++
 display/display.go                                |   5 +
 display/{display.go => graph/graph.go}            |  34 ++-----
 display/graph/linear/linear.go                    | 118 ++++++++++++++++++++++
 go.mod                                            |   2 +
 go.sum                                            |   4 +
 graphql/client/client.go                          |  35 +++++++
 display/display.go => graphql/schema/constants.go |  47 ++++-----
 swctl/main.go                                     |   3 +
 12 files changed, 280 insertions(+), 105 deletions(-)

diff --git a/README.md b/README.md
index a6529b4..f144b79 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,16 @@ and it also has some options and third-level commands.
 | `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
 | `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
 
+### `linear-metrics` second-level command
+`linear-metrics` second-level command is an entrance for all operations related to linear metrics,
+and it also has some options.
+
+| 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 `all_p99`, etc. |
+| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
+
 # Developer guide
 
 ## Compiling and building
diff --git a/display/display.go b/commands/flags/flags.go
similarity index 53%
copy from display/display.go
copy to commands/flags/flags.go
index 63355db..ba41e27 100644
--- a/display/display.go
+++ b/commands/flags/flags.go
@@ -15,37 +15,17 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package display
+package flags
 
-import (
-	"fmt"
-	"strings"
+import "github.com/urfave/cli"
 
-	"github.com/urfave/cli"
+// Flags concatenates the `flags` into one []cli.Flag
+func Flags(flags ...[]cli.Flag) []cli.Flag {
+	var result []cli.Flag
 
-	"github.com/apache/skywalking-cli/display/json"
-	"github.com/apache/skywalking-cli/display/table"
-	"github.com/apache/skywalking-cli/display/yaml"
-)
-
-const (
-	JSON  string = "json"
-	YAML  string = "yaml"
-	TABLE string = "table"
-)
-
-// Display the object in the style specified in flag --display
-func Display(ctx *cli.Context, object interface{}) error {
-	displayStyle := ctx.GlobalString("display")
-
-	switch strings.ToLower(displayStyle) {
-	case JSON:
-		return json.Display(object)
-	case YAML:
-		return yaml.Display(object)
-	case TABLE:
-		return table.Display(object)
-	default:
-		return fmt.Errorf("unsupported display style: %s", displayStyle)
+	for _, flags := range flags {
+		result = append(result, flags...)
 	}
+
+	return result
 }
diff --git a/commands/interceptor/duration.go b/commands/interceptor/duration.go
index 820d6e7..f17c2d9 100644
--- a/commands/interceptor/duration.go
+++ b/commands/interceptor/duration.go
@@ -26,25 +26,9 @@ import (
 	"github.com/apache/skywalking-cli/logger"
 )
 
-var stepFormats = map[schema.Step]string{
-	schema.StepSecond: "2006-01-02 150400",
-	schema.StepMinute: "2006-01-02 1504",
-	schema.StepHour:   "2006-01-02 15",
-	schema.StepDay:    "2006-01-02",
-	schema.StepMonth:  "2006-01",
-}
-
-var stepDuration = map[schema.Step]time.Duration{
-	schema.StepSecond: time.Second,
-	schema.StepMinute: time.Minute,
-	schema.StepHour:   time.Hour,
-	schema.StepDay:    time.Hour * 24,
-	schema.StepMonth:  time.Hour * 24 * 30,
-}
-
 func tryParseTime(unparsed string) (schema.Step, time.Time, error) {
 	var possibleError error = nil
-	for step, layout := range stepFormats {
+	for step, layout := range schema.StepFormats {
 		t, err := time.Parse(layout, unparsed)
 		if err == nil {
 			return step, t, nil
@@ -62,9 +46,9 @@ func DurationInterceptor(ctx *cli.Context) error {
 
 	startTime, endTime, step := ParseDuration(start, end)
 
-	if err := ctx.Set("start", startTime.Format(stepFormats[step])); err != nil {
+	if err := ctx.Set("start", startTime.Format(schema.StepFormats[step])); err != nil {
 		return err
-	} else if err := ctx.Set("end", endTime.Format(stepFormats[step])); err != nil {
+	} else if err := ctx.Set("end", endTime.Format(schema.StepFormats[step])); err != nil {
 		return err
 	} else if err := ctx.Set("step", step.String()); err != nil {
 		return err
@@ -110,12 +94,12 @@ func ParseDuration(start, end string) (startTime, endTime time.Time, step schema
 		if step, startTime, err = tryParseTime(start); err != nil {
 			logger.Log.Fatalln("Unsupported time format:", start, err)
 		}
-		return startTime, startTime.Add(30 * stepDuration[step]), step
+		return startTime, startTime.Add(30 * schema.StepDuration[step]), step
 	} else { // start is absent
 		if step, endTime, err = tryParseTime(end); err != nil {
 			logger.Log.Fatalln("Unsupported time format:", end, err)
 		}
-		return endTime.Add(-30 * stepDuration[step]), endTime, step
+		return endTime.Add(-30 * schema.StepDuration[step]), endTime, step
 	}
 }
 
diff --git a/commands/metrics/linear-metrics.go b/commands/metrics/linear-metrics.go
new file mode 100644
index 0000000..e3148fe
--- /dev/null
+++ b/commands/metrics/linear-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 metrics
+
+import (
+	"github.com/urfave/cli"
+
+	"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/graphql/client"
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
+
+var Command = cli.Command{
+	Name:  "linear-metrics",
+	Usage: "Query linear metrics defined in backend OAL",
+	Flags: flags.Flags(
+		flags.DurationFlags,
+		[]cli.Flag{
+			cli.StringFlag{
+				Name:     "name",
+				Usage:    "metrics `NAME`, such as `all_p99`",
+				Required: true,
+			},
+		},
+	),
+	Before: interceptor.BeforeChain([]cli.BeforeFunc{
+		interceptor.DurationInterceptor,
+	}),
+	Action: func(ctx *cli.Context) error {
+		end := ctx.String("end")
+		start := ctx.String("start")
+		step := ctx.Generic("step")
+		metricsName := ctx.String("name")
+
+		metricsValues := client.LinearIntValues(ctx, schema.MetricCondition{
+			Name: metricsName,
+		}, schema.Duration{
+			Start: start,
+			End:   end,
+			Step:  step.(*model.StepEnumValue).Selected,
+		})
+
+		return display.Display(ctx, metricsValues)
+	},
+}
diff --git a/display/display.go b/display/display.go
index 63355db..48be6ab 100644
--- a/display/display.go
+++ b/display/display.go
@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/apache/skywalking-cli/display/graph"
+
 	"github.com/urfave/cli"
 
 	"github.com/apache/skywalking-cli/display/json"
@@ -32,6 +34,7 @@ const (
 	JSON  string = "json"
 	YAML  string = "yaml"
 	TABLE string = "table"
+	GRAPH string = "graph"
 )
 
 // Display the object in the style specified in flag --display
@@ -45,6 +48,8 @@ func Display(ctx *cli.Context, object interface{}) error {
 		return yaml.Display(object)
 	case TABLE:
 		return table.Display(object)
+	case GRAPH:
+		return graph.Display(object)
 	default:
 		return fmt.Errorf("unsupported display style: %s", displayStyle)
 	}
diff --git a/display/display.go b/display/graph/graph.go
similarity index 55%
copy from display/display.go
copy to display/graph/graph.go
index 63355db..7184360 100644
--- a/display/display.go
+++ b/display/graph/graph.go
@@ -15,37 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package display
+package graph
 
 import (
 	"fmt"
-	"strings"
+	"reflect"
 
-	"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/display/graph/linear"
 )
 
-const (
-	JSON  string = "json"
-	YAML  string = "yaml"
-	TABLE string = "table"
-)
+func Display(object interface{}) error {
+	if reflect.TypeOf(object) != reflect.TypeOf(map[string]float64{}) {
+		return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(object))
+	}
 
-// Display the object in the style specified in flag --display
-func Display(ctx *cli.Context, object interface{}) error {
-	displayStyle := ctx.GlobalString("display")
+	kvs := object.(map[string]float64)
 
-	switch strings.ToLower(displayStyle) {
-	case JSON:
-		return json.Display(object)
-	case YAML:
-		return yaml.Display(object)
-	case TABLE:
-		return table.Display(object)
-	default:
-		return fmt.Errorf("unsupported display style: %s", displayStyle)
-	}
+	return linear.Display(kvs)
 }
diff --git a/display/graph/linear/linear.go b/display/graph/linear/linear.go
new file mode 100644
index 0000000..b6e6d07
--- /dev/null
+++ b/display/graph/linear/linear.go
@@ -0,0 +1,118 @@
+// 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 linear
+
+import (
+	"context"
+	"strings"
+
+	"github.com/mum4k/termdash/widgetapi"
+
+	"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/widgets/linechart"
+)
+
+const RootID = "root"
+
+func newWidgets(inputs map[string]float64) (lineChart *linechart.LineChart, err error) {
+	index := 0
+
+	xLabels := map[int]string{}
+	var yValues []float64
+	for xLabel, yValue := range inputs {
+		xLabels[index] = xLabel
+		index++
+		yValues = append(yValues, yValue)
+	}
+
+	if lineChart, err = linechart.New(
+		linechart.YAxisAdaptive(),
+	); err != nil {
+		return
+	}
+
+	err = lineChart.Series("graph-linear", yValues, linechart.SeriesXLabels(xLabels))
+
+	return lineChart, err
+}
+
+func gridLayout(lineChart widgetapi.Widget) ([]container.Option, error) {
+	widget := grid.Widget(
+		lineChart,
+		container.Border(linestyle.Light),
+		container.BorderTitleAlignCenter(),
+		container.BorderTitle("Press q to quit"),
+	)
+
+	builder := grid.New()
+	builder.Add(widget)
+
+	return builder.Build()
+}
+
+func Display(inputs map[string]float64) error {
+	t, err := termbox.New()
+	if err != nil {
+		return err
+	}
+	defer t.Close()
+
+	c, err := container.New(
+		t,
+		container.ID(RootID),
+		container.PaddingTop(2),
+		container.PaddingRight(2),
+		container.PaddingBottom(2),
+		container.PaddingLeft(2),
+	)
+	if err != nil {
+		return err
+	}
+
+	w, err := newWidgets(inputs)
+	if err != nil {
+		return err
+	}
+
+	gridOpts, err := gridLayout(w)
+	if err != nil {
+		return err
+	}
+
+	err = c.Update(RootID, gridOpts...)
+
+	if err != nil {
+		return err
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	quitter := func(keyboard *terminalapi.Keyboard) {
+		if strings.EqualFold(keyboard.Key.String(), "q") {
+			cancel()
+		}
+	}
+
+	err = termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter))
+
+	return err
+}
diff --git a/go.mod b/go.mod
index c859fb1..9ab591f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,8 @@ go 1.13
 
 require (
 	github.com/machinebox/graphql v0.2.2
+	github.com/mum4k/termdash v0.10.0
+	github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 // indirect
 	github.com/olekukonko/tablewriter v0.0.2
 	github.com/pkg/errors v0.8.1 // indirect
 	github.com/sirupsen/logrus v1.4.2
diff --git a/go.sum b/go.sum
index 0b55309..0203faf 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,10 @@ github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkB
 github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
 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/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-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=
 github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o=
 github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
diff --git a/graphql/client/client.go b/graphql/client/client.go
index fb870a0..1f87a55 100644
--- a/graphql/client/client.go
+++ b/graphql/client/client.go
@@ -20,6 +20,7 @@ package client
 import (
 	"context"
 	"fmt"
+	"time"
 
 	"github.com/machinebox/graphql"
 	"github.com/urfave/cli"
@@ -100,3 +101,37 @@ func SearchService(cliCtx *cli.Context, serviceCode string) (service schema.Serv
 	}
 	return service, nil
 }
+
+func LinearIntValues(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) map[string]float64 {
+	var response map[string]schema.IntValues
+
+	request := graphql.NewRequest(`
+		query ($metric: MetricCondition!, $duration: Duration!) {
+			metrics: getLinearIntValues(metric: $metric, duration: $duration) {
+				values { value }
+			}
+		}
+	`)
+	request.Var("metric", condition)
+	request.Var("duration", duration)
+
+	executeQuery(ctx, request, &response)
+
+	values := metricsToMap(duration, response["metrics"].Values)
+
+	return values
+}
+
+func metricsToMap(duration schema.Duration, kvInts []*schema.KVInt) map[string]float64 {
+	values := map[string]float64{}
+	format := schema.StepFormats[duration.Step]
+	startTime, err := time.Parse(format, duration.Start)
+	if err != nil {
+		logger.Log.Fatalln(err)
+	}
+	step := schema.StepDuration[duration.Step]
+	for idx, value := range kvInts {
+		values[startTime.Add(time.Duration(idx)*step).Format(format)] = float64(value.Value)
+	}
+	return values
+}
diff --git a/display/display.go b/graphql/schema/constants.go
similarity index 53%
copy from display/display.go
copy to graphql/schema/constants.go
index 63355db..f146338 100644
--- a/display/display.go
+++ b/graphql/schema/constants.go
@@ -15,37 +15,24 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package display
+package schema
 
-import (
-	"fmt"
-	"strings"
+import "time"
 
-	"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"
-)
-
-const (
-	JSON  string = "json"
-	YAML  string = "yaml"
-	TABLE string = "table"
-)
-
-// Display the object in the style specified in flag --display
-func Display(ctx *cli.Context, object interface{}) error {
-	displayStyle := ctx.GlobalString("display")
+// StepFormats is a mapping from schema.Step to its time format
+var StepFormats = map[Step]string{
+	StepSecond: "2006-01-02 150400",
+	StepMinute: "2006-01-02 1504",
+	StepHour:   "2006-01-02 15",
+	StepDay:    "2006-01-02",
+	StepMonth:  "2006-01",
+}
 
-	switch strings.ToLower(displayStyle) {
-	case JSON:
-		return json.Display(object)
-	case YAML:
-		return yaml.Display(object)
-	case TABLE:
-		return table.Display(object)
-	default:
-		return fmt.Errorf("unsupported display style: %s", displayStyle)
-	}
+// StepDuration is a mapping from schema.Step to its time.Duration
+var StepDuration = map[Step]time.Duration{
+	StepSecond: time.Second,
+	StepMinute: time.Minute,
+	StepHour:   time.Hour,
+	StepDay:    time.Hour * 24,
+	StepMonth:  time.Hour * 24 * 30,
 }
diff --git a/swctl/main.go b/swctl/main.go
index 3aa564a..dfc5248 100644
--- a/swctl/main.go
+++ b/swctl/main.go
@@ -21,6 +21,8 @@ import (
 	"io/ioutil"
 	"os"
 
+	"github.com/apache/skywalking-cli/commands/metrics"
+
 	"github.com/apache/skywalking-cli/commands/instance"
 
 	"github.com/sirupsen/logrus"
@@ -71,6 +73,7 @@ func main() {
 	app.Commands = []cli.Command{
 		service.Command,
 		instance.Command,
+		metrics.Command,
 	}
 
 	app.Before = interceptor.BeforeChain([]cli.BeforeFunc{