From fd9b93348f51655ec58046aa645cc127c68bbdd2 Mon Sep 17 00:00:00 2001 From: Mrproliu <741550557@qq.com> Date: Wed, 14 Sep 2022 10:33:34 +0800 Subject: [PATCH] Add the sub-commands for query sorted metrics/records --- CHANGES.md | 1 + .../graphqls/metrics/SampledRecords.graphql | 23 +++++ .../commands/metrics/aggregation/metrics.go | 84 +++++++++++++++++++ .../metrics/aggregation/sampled-record.go | 78 +++++++++++++++++ .../metrics/aggregation/sorted-condition.go | 73 ++++++++++++++++ internal/commands/metrics/aggregation/topn.go | 42 +--------- internal/commands/metrics/metrics.go | 2 + pkg/display/display.go | 1 + pkg/graphql/metrics/metrics.go | 12 +++ 9 files changed, 277 insertions(+), 39 deletions(-) create mode 100644 assets/graphqls/metrics/SampledRecords.graphql create mode 100644 internal/commands/metrics/aggregation/metrics.go create mode 100644 internal/commands/metrics/aggregation/sampled-record.go create mode 100644 internal/commands/metrics/aggregation/sorted-condition.go diff --git a/CHANGES.md b/CHANGES.md index 31b9d691..cc0477ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ Release Notes. - Add the sub-command `profiling ebpf create network` and `profiling ebpf keep network` to create and keep the network eBPF profiling task.(#158) - Add the sub-command `dependency process` to query the process relation.(#158) - Support query the metrics of process relation.(#158) +- Add the sub-command `metrics sorted` and `metrics sampled-record` to query the sorted metrics/records.(#163) 0.10.0 ------------------ diff --git a/assets/graphqls/metrics/SampledRecords.graphql b/assets/graphqls/metrics/SampledRecords.graphql new file mode 100644 index 00000000..9fc1a6c0 --- /dev/null +++ b/assets/graphqls/metrics/SampledRecords.graphql @@ -0,0 +1,23 @@ +# 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. + +query ($condition:TopNCondition!, $duration: Duration!) { + result: readSampledRecords(condition: $condition, duration: $duration) { + name + value + } +} \ No newline at end of file diff --git a/internal/commands/metrics/aggregation/metrics.go b/internal/commands/metrics/aggregation/metrics.go new file mode 100644 index 00000000..4e1e8d14 --- /dev/null +++ b/internal/commands/metrics/aggregation/metrics.go @@ -0,0 +1,84 @@ +// 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 ( + api "skywalking.apache.org/repo/goapi/query" + + "github.com/urfave/cli/v2" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/internal/logger" + "github.com/apache/skywalking-cli/internal/model" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/metrics" +) + +var SortedMetrics = &cli.Command{ + Name: "sorted", + Usage: "query the top entities sorted by the specified metrics", + ArgsUsage: "", + UsageText: `Query the top entities sorted by the specified metrics. + +Examples: +1. Query the top 5 services whose sla are largest: +$ swctl metrics sorted --name service_sla 5 + +2. Query the top 5 endpoints whose sla is largest: +$ swctl metrics sorted --name endpoint_sla 5 + +3. Query the top 5 instances of service "boutique::adservice" whose sla are largest: +$ swctl metrics sorted --name service_instance_sla --service-name boutique::adservice 5 +`, + Flags: flags.Flags( + flags.DurationFlags, + flags.MetricsFlags, + flags.ServiceFlags, + []cli.Flag{ + &cli.GenericFlag{ + Name: "order", + Usage: "the `order` by which the top entities are sorted", + Value: &model.OrderEnumValue{ + Enum: api.AllOrder, + Default: api.OrderDes, + Selected: api.OrderDes, + }, + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.DurationInterceptor, + interceptor.ParseService(false), + ), + Action: func(ctx *cli.Context) error { + condition, duration, err := buildSortedCondition(ctx, true) + if err != nil { + return err + } + + logger.Log.Debugln(condition.Name, condition.Scope, condition.TopN) + metricsValues, err := metrics.SortMetrics(ctx, *condition, *duration) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: metricsValues}) + }, +} diff --git a/internal/commands/metrics/aggregation/sampled-record.go b/internal/commands/metrics/aggregation/sampled-record.go new file mode 100644 index 00000000..6b38463d --- /dev/null +++ b/internal/commands/metrics/aggregation/sampled-record.go @@ -0,0 +1,78 @@ +// 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 ( + api "skywalking.apache.org/repo/goapi/query" + + "github.com/urfave/cli/v2" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/internal/logger" + "github.com/apache/skywalking-cli/internal/model" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/metrics" +) + +var SampledRecords = &cli.Command{ + Name: "sampled-record", + Usage: "query the top entities sorted by the specified records", + ArgsUsage: "", + UsageText: `Query the top entities sorted by the specified records. + +Examples: +1. Query the top 5 database statements whose execute duration are largest: +$ swctl metrics sampled-record --name top_n_database_statement 5 +`, + Flags: flags.Flags( + flags.DurationFlags, + flags.MetricsFlags, + flags.ServiceFlags, + []cli.Flag{ + &cli.GenericFlag{ + Name: "order", + Usage: "the `order` by which the top entities are sorted", + Value: &model.OrderEnumValue{ + Enum: api.AllOrder, + Default: api.OrderDes, + Selected: api.OrderDes, + }, + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.DurationInterceptor, + interceptor.ParseService(false), + ), + Action: func(ctx *cli.Context) error { + condition, duration, err := buildSortedCondition(ctx, false) + if err != nil { + return err + } + + logger.Log.Debugln(condition.Name, condition.Scope, condition.TopN) + sampledRecords, err := metrics.SampledRecords(ctx, *condition, *duration) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: sampledRecords}) + }, +} diff --git a/internal/commands/metrics/aggregation/sorted-condition.go b/internal/commands/metrics/aggregation/sorted-condition.go new file mode 100644 index 00000000..9d506246 --- /dev/null +++ b/internal/commands/metrics/aggregation/sorted-condition.go @@ -0,0 +1,73 @@ +// 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" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/model" + "github.com/apache/skywalking-cli/pkg/graphql/utils" + + "github.com/urfave/cli/v2" + + api "skywalking.apache.org/repo/goapi/query" +) + +// buildSortedCondition from context of cli, the first argument must be the count of top N +func buildSortedCondition(ctx *cli.Context, parseScope bool) (*api.TopNCondition, *api.Duration, error) { + start := ctx.String("start") + end := ctx.String("end") + step := ctx.Generic("step").(*model.StepEnumValue).Selected + + metricsName := ctx.String("name") + var scope *api.Scope + if parseScope { + tmp := utils.ParseScopeInTop(metricsName) + scope = &tmp + } + order := ctx.Generic("order").(*model.OrderEnumValue).Selected + topN := 5 + parentServiceID := ctx.String("service-id") + parentService, normal, err := interceptor.ParseServiceID(parentServiceID) + if err != nil { + return nil, nil, err + } + + if ctx.NArg() > 0 { + nn, err2 := strconv.Atoi(ctx.Args().First()) + if err2 != nil { + return nil, nil, fmt.Errorf("the 1st argument must be a number: %v", err2) + } + topN = nn + } + + return &api.TopNCondition{ + Name: metricsName, + ParentService: &parentService, + Normal: &normal, + Scope: scope, + TopN: topN, + Order: order, + }, &api.Duration{ + Start: start, + End: end, + Step: step, + }, nil +} diff --git a/internal/commands/metrics/aggregation/topn.go b/internal/commands/metrics/aggregation/topn.go index d2ba896d..7a537b7b 100644 --- a/internal/commands/metrics/aggregation/topn.go +++ b/internal/commands/metrics/aggregation/topn.go @@ -18,9 +18,6 @@ package aggregation import ( - "fmt" - "strconv" - api "skywalking.apache.org/repo/goapi/query" "github.com/urfave/cli/v2" @@ -32,7 +29,6 @@ import ( "github.com/apache/skywalking-cli/pkg/display" "github.com/apache/skywalking-cli/pkg/display/displayable" "github.com/apache/skywalking-cli/pkg/graphql/metrics" - "github.com/apache/skywalking-cli/pkg/graphql/utils" ) var TopN = &cli.Command{ @@ -72,45 +68,13 @@ $ swctl metrics top --name service_instance_sla --service-name boutique::adservi interceptor.ParseService(false), ), Action: func(ctx *cli.Context) error { - start := ctx.String("start") - end := ctx.String("end") - step := ctx.Generic("step").(*model.StepEnumValue).Selected - - metricsName := ctx.String("name") - scope := utils.ParseScopeInTop(metricsName) - order := ctx.Generic("order").(*model.OrderEnumValue).Selected - topN := 5 - parentServiceID := ctx.String("service-id") - parentService, normal, err := interceptor.ParseServiceID(parentServiceID) + condition, duration, err := buildSortedCondition(ctx, true) if err != nil { return err } - if ctx.NArg() > 0 { - nn, err2 := strconv.Atoi(ctx.Args().First()) - if err2 != nil { - return fmt.Errorf("the 1st argument must be a number: %v", err2) - } - topN = nn - } - - duration := api.Duration{ - Start: start, - End: end, - Step: step, - } - - logger.Log.Debugln(metricsName, scope, topN) - - metricsValues, err := metrics.SortMetrics(ctx, api.TopNCondition{ - Name: metricsName, - ParentService: &parentService, - Normal: &normal, - Scope: &scope, - TopN: topN, - Order: order, - }, duration) - + logger.Log.Debugln(condition.Name, condition.Scope, condition.TopN) + metricsValues, err := metrics.SortMetrics(ctx, *condition, *duration) if err != nil { return err } diff --git a/internal/commands/metrics/metrics.go b/internal/commands/metrics/metrics.go index ad2fe379..5ae661bb 100644 --- a/internal/commands/metrics/metrics.go +++ b/internal/commands/metrics/metrics.go @@ -38,6 +38,8 @@ var Command = &cli.Command{ linear.Multiple, thermodynamic.Command, aggregation.TopN, + aggregation.SortedMetrics, + aggregation.SampledRecords, list.Command, }, } diff --git a/pkg/display/display.go b/pkg/display/display.go index 2004e131..6e3f8226 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -44,6 +44,7 @@ const ( var style = map[string]string{"dashboard global": "graph", "dashboard global-metrics": "graph", "metrics top": "table", + "metrics sorted": "table", "metrics linear": "graph", "metrics list": "table", "service list": "table", diff --git a/pkg/graphql/metrics/metrics.go b/pkg/graphql/metrics/metrics.go index 65cadcc1..39397223 100644 --- a/pkg/graphql/metrics/metrics.go +++ b/pkg/graphql/metrics/metrics.go @@ -92,6 +92,18 @@ func SortMetrics(ctx *cli.Context, condition api.TopNCondition, duration api.Dur return response["result"], err } +func SampledRecords(ctx *cli.Context, condition api.TopNCondition, duration api.Duration) ([]*api.SelectedRecord, error) { + var response map[string][]*api.SelectedRecord + + request := graphql.NewRequest(assets.Read("graphqls/metrics/SampledRecords.graphql")) + request.Var("condition", condition) + request.Var("duration", duration) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + func ListMetrics(ctx *cli.Context, regex string) ([]*api.MetricDefinition, error) { var response map[string][]*api.MetricDefinition request := graphql.NewRequest(assets.Read("graphqls/metrics/ListMetrics.graphql"))