提交 6e0db0aa 编写于 作者: K kezhenxu94 提交者: wu-sheng

[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
上级 045abf77
......@@ -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
......
// 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 flags
import "github.com/urfave/cli"
// Flags concatenates the `flags` into one []cli.Flag
func Flags(flags ...[]cli.Flag) []cli.Flag {
var result []cli.Flag
for _, flags := range flags {
result = append(result, flags...)
}
return result
}
......@@ -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
}
}
......
// 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)
},
}
......@@ -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)
}
......
// 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 graph
import (
"fmt"
"reflect"
"github.com/apache/skywalking-cli/display/graph/linear"
)
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))
}
kvs := object.(map[string]float64)
return linear.Display(kvs)
}
// 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
}
......@@ -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
......
......@@ -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
}
// 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 schema
import "time"
// 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",
}
// 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,
}
......@@ -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{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册