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 2020/03/07 15:07:02 UTC

[skywalking-cli] branch master updated: [Feature] Support top N entities and thermodynamic metrics (#33)

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 c38c595  [Feature] Support top N entities and thermodynamic metrics (#33)
c38c595 is described below

commit c38c595aab924430ad9dcc04e695ed888bb1f28e
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Sat Mar 7 23:06:53 2020 +0800

    [Feature] Support top N entities and thermodynamic metrics (#33)
    
    ### Motivation
    
    Support top N entities and thermodynamic metrics commands
    
    ### Result
    
    - Commands `metrics top n` and `thermodynamic` are added
    
    - Closes https://github.com/apache/skywalking/issues/3898
    
    - Closes https://github.com/apache/skywalking/issues/3897
---
 .golangci.yml                                   |   4 +-
 README.md                                       |  83 ++++++++++++-
 commands/metrics/aggregation/topn.go            | 112 +++++++++++++++++
 commands/metrics/metrics.go                     |   6 +
 commands/metrics/thermodynamic/thermodynamic.go |  67 ++++++++++
 commands/model/{step.go => order.go}            |  28 ++---
 commands/model/step.go                          |   2 +-
 graphql/aggregation/aggregation.go              | 155 ++++++++++++++++++++++++
 graphql/metrics/metrics.go                      |  18 +++
 9 files changed, 455 insertions(+), 20 deletions(-)

diff --git a/.golangci.yml b/.golangci.yml
index 26b94e2..433731a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -24,7 +24,7 @@ linters-settings:
   maligned:
     suggest-new: true
   dupl:
-    threshold: 100
+    threshold: 200
   goconst:
     min-len: 2
     min-occurrences: 2
@@ -93,4 +93,4 @@ linters:
 service:
   golangci-lint-version: 1.20.x
   prepare:
-    - echo "here I can run custom commands, but no preparation needed for this repo"
\ No newline at end of file
+    - echo "here I can run custom commands, but no preparation needed for this repo"
diff --git a/README.md b/README.md
index 0467d09..9fba39b 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,10 @@ This section covers all the available commands in SkyWalking CLI and their usage
 | `--config` | from where the default options values will be loaded | `~/.skywalking.yml` |
 | `--debug` | enable debug mode, will print more detailed information at runtime | `false` |
 | `--base-url` | base url of GraphQL backend | `http://127.0.0.1:12800/graphql` |
-| `--display` | display style when printing the query result, supported styles are: `json`, `yaml`, `table` | `json` |
+| `--display` | display style when printing the query result, supported styles are: `json`, `yaml`, `table`, `graph` | `json` |
+
+Note that not all display styles (except for `json` and `yaml`) are supported in all commands due to data formats incompatibilities and the limits of
+Ascii Graph, like coloring in terminal, so please use `json`  or `yaml` instead.
 
 ### `service`
 
@@ -197,6 +200,36 @@ This section covers all the available commands in SkyWalking CLI and their usage
 
 </details>
 
+#### `metrics top <n>`
+
+<details>
+
+<summary>metrics top 3 [--start=start-time] [--end=end-time] --name endpoint_sla [--service-id 3]</summary>
+
+| 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 `service_sla`, etc. |
+| `--service-id` | service ID that are required by the metric type, such as service IDs for `service_sla` |
+| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
+| arguments | the first argument is the number of top entities | `3` |
+
+</details>
+
+#### `metrics thermodynamic`
+
+<details>
+
+<summary>metrics thermodynamic --name=thermodynamic name</summary>
+
+| 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 `service_sla`, etc. |
+| `--start` | See [Common options](#common-options) | See [Common options](#common-options) |
+| `--end` | See [Common options](#common-options) | See [Common options](#common-options) |
+
+</details>
+
 # Use Cases
 
 <details>
@@ -244,7 +277,7 @@ $ ./bin/swctl endpoint ls --service-id=3
 otherwise,
 
 ```shell
-./bin/swctl service ls projectC | jq '.[].id' | xargs ./bin/swctl-latest-darwin-amd64 endpoint ls --service-id 
+./bin/swctl service ls projectC | jq '.[].id' | xargs ./bin/swctl endpoint ls --service-id 
 [{"id":"22","name":"/projectC/{value}"}]
 ```
 
@@ -331,7 +364,7 @@ $ ./bin/swctl service ls projectC | jq '.[0].id' | xargs ./bin/swctl endpoint ls
 <summary>Query multiple metrics values for all percentiles</summary>
 
 ```shell
-$ ./bin/swctl-latest-darwin-amd64 --display=graph --debug metrics multiple-linear --name all_percentile
+$ ./bin/swctl --display=graph --debug metrics multiple-linear --name all_percentile
 
 ┌PRESS Q TO QUIT───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │┌───────────────────────────────#0───────────────────────────────┐┌───────────────────────────────#1───────────────────────────────┐┌─────────────────────────────────#2─────────────────────────────────┐│
@@ -395,6 +428,50 @@ $ ./bin/swctl-latest-darwin-amd64 --display=graph --debug metrics multiple-linea
 
 <details>
 
+<summary>Query the top 5 services whose sla is largest</summary>
+
+```shell
+$ ./bin/swctl metrics top 5 --name service_sla        
+[{"name":"projectB","id":"2","value":10000},{"name":"projectC","id":"3","value":10000},{"name":"projectA","id":"4","value":10000},{"name":"projectD","id":"5","value":10000}]
+```
+
+</details>
+
+<details>
+
+<summary>Query the top 5 instances whose sla is largest, of service (id = 3)</summary>
+
+```shell
+$ ./bin/swctl metrics top 5 --name service_instance_sla --service-id 3        
+[{"name":"projectC-pid:30335@skywalking-server-0002","id":"13","value":10000},{"name":"projectC-pid:22037@skywalking-server-0001","id":"2","value":10000}]
+```
+
+</details>
+
+<details>
+
+<summary>Query the top 5 endpoints whose sla is largest, of service (id = 3)</summary>
+
+```shell
+$ ./bin/swctl metrics top 5 --name endpoint_sla --service-id 3        
+[{"name":"/projectC/{value}","id":"4","value":10000}]
+```
+
+</details>
+
+<details>
+
+<summary>Query the overall heatmap</summary>
+
+```shell
+$  ./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 [...]
+```
+
+</details>
+
+<details>
+
 <summary>Automatically convert to server side timezone</summary>
 
 if your backend nodes are deployed in docker and the timezone is UTC, you may not want to convert your timezone to UTC every time you type a command, `--timezone` comes to your rescue.
diff --git a/commands/metrics/aggregation/topn.go b/commands/metrics/aggregation/topn.go
new file mode 100644
index 0000000..77876f6
--- /dev/null
+++ b/commands/metrics/aggregation/topn.go
@@ -0,0 +1,112 @@
+// 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 aggregation
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/apache/skywalking-cli/commands/interceptor"
+
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/commands/flags"
+	"github.com/apache/skywalking-cli/commands/model"
+	"github.com/apache/skywalking-cli/display"
+	"github.com/apache/skywalking-cli/graphql/aggregation"
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
+
+var TopN = cli.Command{
+	Name:      "top",
+	Usage:     "query top `n` entities",
+	ArgsUsage: "<n>",
+	Flags: flags.Flags(
+		flags.DurationFlags,
+		[]cli.Flag{
+			cli.StringFlag{
+				Name:     "name",
+				Usage:    "`metrics name`, which should be defined in OAL script",
+				Required: true,
+			},
+			cli.GenericFlag{
+				Name:  "order",
+				Usage: "the `order` by which the top entities are sorted",
+				Value: &model.OrderEnumValue{
+					Enum:     schema.AllOrder,
+					Default:  schema.OrderDes,
+					Selected: schema.OrderDes,
+				},
+			},
+			cli.StringFlag{
+				Name:     "service-id",
+				Usage:    "the `service id` whose instances/endpoints are to be fetch, if applicable",
+				Required: false,
+			},
+		},
+	),
+	Before: interceptor.BeforeChain([]cli.BeforeFunc{
+		interceptor.TimezoneInterceptor,
+		interceptor.DurationInterceptor,
+	}),
+	Action: func(ctx *cli.Context) error {
+		name := ctx.String("name")
+		start := ctx.String("start")
+		end := ctx.String("end")
+		step := ctx.Generic("step").(*model.StepEnumValue).Selected
+		order := ctx.Generic("order").(*model.OrderEnumValue).Selected
+		serviceID := ctx.String("service-id")
+
+		topN := 5
+
+		if ctx.NArg() > 0 {
+			nn, err := strconv.Atoi(ctx.Args().First())
+			if err != nil {
+				return fmt.Errorf("the 1st argument must be a number")
+			}
+			topN = nn
+		}
+
+		duration := schema.Duration{
+			Start: start,
+			End:   end,
+			Step:  step,
+		}
+
+		var metricsValues []schema.TopNEntity
+
+		if strings.HasPrefix(name, "service_instance") {
+			if serviceID == "" {
+				metricsValues = aggregation.AllServiceInstanceTopN(ctx, name, topN, duration, order)
+			} else {
+				metricsValues = aggregation.ServiceInstanceTopN(ctx, serviceID, name, topN, duration, order)
+			}
+		} else if strings.HasPrefix(name, "endpoint_") {
+			if serviceID == "" {
+				metricsValues = aggregation.AllEndpointTopN(ctx, name, topN, duration, order)
+			} else {
+				metricsValues = aggregation.EndpointTopN(ctx, serviceID, name, topN, duration, order)
+			}
+		} else if strings.HasPrefix(name, "service_") {
+			metricsValues = aggregation.ServiceTopN(ctx, name, topN, duration, order)
+		}
+
+		return display.Display(ctx, metricsValues)
+	},
+}
diff --git a/commands/metrics/metrics.go b/commands/metrics/metrics.go
index 8f941b9..d1b4a8f 100644
--- a/commands/metrics/metrics.go
+++ b/commands/metrics/metrics.go
@@ -20,6 +20,10 @@ package metrics
 import (
 	"github.com/urfave/cli"
 
+	"github.com/apache/skywalking-cli/commands/metrics/aggregation"
+
+	"github.com/apache/skywalking-cli/commands/metrics/thermodynamic"
+
 	"github.com/apache/skywalking-cli/commands/metrics/linear"
 	"github.com/apache/skywalking-cli/commands/metrics/single"
 )
@@ -31,5 +35,7 @@ var Command = cli.Command{
 		single.Command,
 		linear.Single,
 		linear.Multiple,
+		thermodynamic.Command,
+		aggregation.TopN,
 	},
 }
diff --git a/commands/metrics/thermodynamic/thermodynamic.go b/commands/metrics/thermodynamic/thermodynamic.go
new file mode 100644
index 0000000..5eefb3a
--- /dev/null
+++ b/commands/metrics/thermodynamic/thermodynamic.go
@@ -0,0 +1,67 @@
+// 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 thermodynamic
+
+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/metrics"
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
+
+var Command = cli.Command{
+	Name:      "thermodynamic",
+	ShortName: "td",
+	Usage:     "Query thermodynamic metrics defined in backend OAL",
+	Flags: flags.Flags(
+		flags.DurationFlags,
+		[]cli.Flag{
+			cli.StringFlag{
+				Name:     "name",
+				Usage:    "metrics `NAME`, which should be defined in OAL script",
+				Required: true,
+			},
+		},
+	),
+	Before: interceptor.BeforeChain([]cli.BeforeFunc{
+		interceptor.TimezoneInterceptor,
+		interceptor.DurationInterceptor,
+	}),
+	Action: func(ctx *cli.Context) error {
+		end := ctx.String("end")
+		start := ctx.String("start")
+		step := ctx.Generic("step")
+		metricsName := ctx.String("name")
+
+		duration := schema.Duration{
+			Start: start,
+			End:   end,
+			Step:  step.(*model.StepEnumValue).Selected,
+		}
+
+		metricsValues := metrics.Thermodynamic(ctx, schema.MetricCondition{
+			Name: metricsName,
+		}, duration)
+
+		return display.Display(ctx, metricsValues)
+	},
+}
diff --git a/commands/model/step.go b/commands/model/order.go
similarity index 63%
copy from commands/model/step.go
copy to commands/model/order.go
index 719799d..77a20ba 100644
--- a/commands/model/step.go
+++ b/commands/model/order.go
@@ -24,29 +24,29 @@ import (
 	"github.com/apache/skywalking-cli/graphql/schema"
 )
 
-// StepEnumValue defines the values domain of --step option
-type StepEnumValue struct {
-	Enum     []schema.Step
-	Default  schema.Step
-	Selected schema.Step
+// OrderEnumValue defines the values domain of --order option
+type OrderEnumValue struct {
+	Enum     []schema.Order
+	Default  schema.Order
+	Selected schema.Order
 }
 
-// Set the --step value, from raw string to StepEnumValue
-func (s *StepEnumValue) Set(value string) error {
+// Set the --order value, from raw string to OrderEnumValue
+func (s *OrderEnumValue) Set(value string) error {
 	for _, enum := range s.Enum {
-		if enum.String() == value {
+		if strings.EqualFold(enum.String(), value) {
 			s.Selected = enum
 			return nil
 		}
 	}
-	steps := make([]string, len(schema.AllStep))
-	for i, step := range schema.AllStep {
-		steps[i] = step.String()
+	orders := make([]string, len(schema.AllOrder))
+	for i, order := range schema.AllOrder {
+		orders[i] = order.String()
 	}
-	return fmt.Errorf("allowed steps are %s", strings.Join(steps, ", "))
+	return fmt.Errorf("allowed orders are %s", strings.Join(orders, ", "))
 }
 
-// String representation of the step
-func (s StepEnumValue) String() string {
+// String representation of the order
+func (s OrderEnumValue) String() string {
 	return s.Selected.String()
 }
diff --git a/commands/model/step.go b/commands/model/step.go
index 719799d..8f363be 100644
--- a/commands/model/step.go
+++ b/commands/model/step.go
@@ -34,7 +34,7 @@ type StepEnumValue struct {
 // Set the --step value, from raw string to StepEnumValue
 func (s *StepEnumValue) Set(value string) error {
 	for _, enum := range s.Enum {
-		if enum.String() == value {
+		if strings.EqualFold(enum.String(), value) {
 			s.Selected = enum
 			return nil
 		}
diff --git a/graphql/aggregation/aggregation.go b/graphql/aggregation/aggregation.go
new file mode 100644
index 0000000..fc11392
--- /dev/null
+++ b/graphql/aggregation/aggregation.go
@@ -0,0 +1,155 @@
+// 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 aggregation
+
+import (
+	"github.com/machinebox/graphql"
+	"github.com/urfave/cli"
+
+	"github.com/apache/skywalking-cli/graphql/client"
+	"github.com/apache/skywalking-cli/graphql/schema"
+)
+
+func ServiceTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
+	var response map[string][]schema.TopNEntity
+
+	request := graphql.NewRequest(`
+		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+			result: getServiceTopN(
+				duration: $duration,
+				name: $name,
+				topN: $topN,
+				order: $order
+			) {
+				id name value
+			}
+		}
+	`)
+	request.Var("name", name)
+	request.Var("topN", topN)
+	request.Var("duration", duration)
+	request.Var("order", order)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func AllServiceInstanceTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
+	var response map[string][]schema.TopNEntity
+
+	request := graphql.NewRequest(`
+		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+			result: getAllServiceInstanceTopN(
+				duration: $duration,
+				name: $name,
+				topN: $topN,
+				order: $order
+			) {
+				id name value
+			}
+		}
+	`)
+	request.Var("name", name)
+	request.Var("topN", topN)
+	request.Var("duration", duration)
+	request.Var("order", order)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func ServiceInstanceTopN(ctx *cli.Context, serviceID, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
+	var response map[string][]schema.TopNEntity
+
+	request := graphql.NewRequest(`
+		query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+			result: getServiceInstanceTopN(
+				serviceId: $serviceId,
+				duration: $duration,
+				name: $name,
+				topN: $topN,
+				order: $order
+			) {
+				id name value
+			}
+		}
+	`)
+	request.Var("serviceId", serviceID)
+	request.Var("name", name)
+	request.Var("topN", topN)
+	request.Var("duration", duration)
+	request.Var("order", order)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func AllEndpointTopN(ctx *cli.Context, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
+	var response map[string][]schema.TopNEntity
+
+	request := graphql.NewRequest(`
+		query ($name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+			result: getAllEndpointTopN(
+				duration: $duration,
+				name: $name,
+				topN: $topN,
+				order: $order
+			) {
+				id name value
+			}
+		}
+	`)
+	request.Var("name", name)
+	request.Var("topN", topN)
+	request.Var("duration", duration)
+	request.Var("order", order)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
+
+func EndpointTopN(ctx *cli.Context, serviceID, name string, topN int, duration schema.Duration, order schema.Order) []schema.TopNEntity {
+	var response map[string][]schema.TopNEntity
+
+	request := graphql.NewRequest(`
+		query ($serviceId: ID!, $name: String!, $topN: Int!, $duration: Duration!, $order: Order!) {
+			result: getEndpointTopN(
+				serviceId: $serviceId,
+				duration: $duration,
+				name: $name,
+				topN: $topN,
+				order: $order
+			) {
+				id name value
+			}
+		}
+	`)
+	request.Var("serviceId", serviceID)
+	request.Var("name", name)
+	request.Var("topN", topN)
+	request.Var("duration", duration)
+	request.Var("order", order)
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["result"]
+}
diff --git a/graphql/metrics/metrics.go b/graphql/metrics/metrics.go
index c086e51..68cf36d 100644
--- a/graphql/metrics/metrics.go
+++ b/graphql/metrics/metrics.go
@@ -80,3 +80,21 @@ func MultipleLinearIntValues(ctx *cli.Context, condition schema.MetricCondition,
 
 	return response["metrics"]
 }
+
+func Thermodynamic(ctx *cli.Context, condition schema.MetricCondition, duration schema.Duration) schema.Thermodynamic {
+	request := graphql.NewRequest(`
+		query ($metric: MetricCondition!, $duration: Duration!) {
+			metrics: getThermodynamic(metric: $metric, duration: $duration) {
+				nodes responseTimeStep: axisYStep
+			}
+		}
+	`)
+	request.Var("metric", condition)
+	request.Var("duration", duration)
+
+	var response map[string]schema.Thermodynamic
+
+	client.ExecuteQueryOrFail(ctx, request, &response)
+
+	return response["metrics"]
+}