You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/03/07 10:21:16 UTC
[skywalking-cli] 01/01: [Feature] Support top N entities and
thermodynamic metrics
This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch feature/thermodynamic
in repository https://gitbox.apache.org/repos/asf/skywalking-cli.git
commit dd20523764aac52ba80692a722ba5ce373b6c3cc
Author: kezhenxu94 <ke...@163.com>
AuthorDate: Sat Mar 7 18:20:56 2020 +0800
[Feature] Support top N entities and thermodynamic metrics
### 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 | 49 ++++++++
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, 424 insertions(+), 17 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..b13ee05 100644
--- a/README.md
+++ b/README.md
@@ -197,6 +197,22 @@ 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>
+
# Use Cases
<details>
@@ -395,6 +411,39 @@ $ ./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-latest-darwin-amd64 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-latest-darwin-amd64 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-latest-darwin-amd64 metrics top 5 --name endpoint_sla --service-id 3
+[{"name":"/projectC/{value}","id":"4","value":10000}]
+```
+
+</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"]
+}