diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 423387a..1b24526 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -90,7 +90,7 @@ jobs: go-version: 1.18 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: v1.50.0 args: --timeout 5m diff --git a/CHANGES.md b/CHANGES.md index a7323dc..410cea7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ Release Notes. ### Features +* add the sub-command `profiling async` for async-profiler query API by @zhengziyi0117 in https://github.com/apache/skywalking-cli/pull/203 + ### Bug Fixes 0.14.0 diff --git a/assets/graphqls/profiling/asyncprofiler/CreateTask.graphql b/assets/graphqls/profiling/asyncprofiler/CreateTask.graphql new file mode 100644 index 0000000..2e53fbd --- /dev/null +++ b/assets/graphqls/profiling/asyncprofiler/CreateTask.graphql @@ -0,0 +1,24 @@ +# 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. + +mutation ($condition: AsyncProfilerTaskCreationRequest!) { + result: createAsyncProfilerTask(asyncProfilerTaskCreationRequest: $condition) { + errorReason + code + id + } +} \ No newline at end of file diff --git a/assets/graphqls/profiling/asyncprofiler/GetAnalysis.graphql b/assets/graphqls/profiling/asyncprofiler/GetAnalysis.graphql new file mode 100644 index 0000000..a8cd925 --- /dev/null +++ b/assets/graphqls/profiling/asyncprofiler/GetAnalysis.graphql @@ -0,0 +1,31 @@ +# 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: AsyncProfilerAnalyzationRequest!) { + result: queryAsyncProfilerAnalyze(request: $condition) { + tree { + type + elements { + id + parentId + codeSignature + total + self + } + } + } +} \ No newline at end of file diff --git a/assets/graphqls/profiling/asyncprofiler/GetTaskList.graphql b/assets/graphqls/profiling/asyncprofiler/GetTaskList.graphql new file mode 100644 index 0000000..f62ca30 --- /dev/null +++ b/assets/graphqls/profiling/asyncprofiler/GetTaskList.graphql @@ -0,0 +1,31 @@ +# 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: AsyncProfilerTaskListRequest!) { + result: queryAsyncProfilerTaskList(request: $condition) { + errorReason + tasks { + serviceId + serviceInstanceIds + createTime + events + duration + execArgs + id + } + } +} diff --git a/assets/graphqls/profiling/asyncprofiler/GetTaskProgress.graphql b/assets/graphqls/profiling/asyncprofiler/GetTaskProgress.graphql new file mode 100644 index 0000000..a81c4c0 --- /dev/null +++ b/assets/graphqls/profiling/asyncprofiler/GetTaskProgress.graphql @@ -0,0 +1,30 @@ +# 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 ($taskId: String!){ + result: queryAsyncProfilerTaskProgress(taskId: $taskId) { + errorInstanceIds + successInstanceIds + logs { + id + instanceId + instanceName + operationType + operationTime + } + } +} \ No newline at end of file diff --git a/dist/LICENSE b/dist/LICENSE index b53f5be..4bb722f 100644 --- a/dist/LICENSE +++ b/dist/LICENSE @@ -213,7 +213,7 @@ The text of each license is also included at licenses/license-[project].txt. k8s.io/utils v0.0.0-20210802155522-efc7438f0176 Apache-2.0 sigs.k8s.io/controller-runtime v0.10.0 Apache-2.0 sigs.k8s.io/structured-merge-diff/v4 v4.1.2 Apache-2.0 - skywalking.apache.org/repo/goapi v0.0.0-20240227092755-edee3273b361 Apache-2.0 + skywalking.apache.org/repo/goapi v0.0.0-20241023080050-2514649a8007 Apache-2.0 ======================================================================== BSD-2-Clause licenses diff --git a/go.mod b/go.mod index c030ac6..4a771a3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 k8s.io/apimachinery v0.22.1 sigs.k8s.io/controller-runtime v0.10.0 - skywalking.apache.org/repo/goapi v0.0.0-20240227092755-edee3273b361 + skywalking.apache.org/repo/goapi v0.0.0-20241023080050-2514649a8007 ) require ( diff --git a/go.sum b/go.sum index c815258..14f447f 100644 --- a/go.sum +++ b/go.sum @@ -879,5 +879,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3 sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -skywalking.apache.org/repo/goapi v0.0.0-20240227092755-edee3273b361 h1:FCGGU4Tut3LI/zMRXSgJgUL/kmSQ4b7QktFgRBhqaDs= -skywalking.apache.org/repo/goapi v0.0.0-20240227092755-edee3273b361/go.mod h1:+n8BMuS8eRdzdnGh15ElRGBXPi0eYZSs2TKySBDmRTE= +skywalking.apache.org/repo/goapi v0.0.0-20241023080050-2514649a8007 h1:MQU9yOZxgbs7fU145wd400/E3ES/HWoxTtTCudaCBoA= +skywalking.apache.org/repo/goapi v0.0.0-20241023080050-2514649a8007/go.mod h1:+n8BMuS8eRdzdnGh15ElRGBXPi0eYZSs2TKySBDmRTE= diff --git a/internal/commands/interceptor/instance.go b/internal/commands/interceptor/instance.go index 78f5ad1..d381717 100644 --- a/internal/commands/interceptor/instance.go +++ b/internal/commands/interceptor/instance.go @@ -30,6 +30,8 @@ const ( instanceNameFlagName = "instance-name" destInstanceIDFlagName = "dest-instance-id" destInstanceNameFlagName = "dest-instance-name" + InstanceIDListFlagName = "instance-id-list" + instanceNameListFlagName = "instance-name-list" ) // ParseInstance parses the service instance id or service instance name, @@ -44,6 +46,18 @@ func ParseInstance(required bool) func(*cli.Context) error { } } +// ParseInstanceList parses the service instance id slice or service instance name slice, +// and converts the present one to the missing one. +// See flags.InstanceSliceFlags. +func ParseInstanceList(required bool) func(*cli.Context) error { + return func(ctx *cli.Context) error { + if err := ParseService(required)(ctx); err != nil { + return err + } + return parseInstanceList(required, InstanceIDListFlagName, instanceNameListFlagName, serviceIDFlagName)(ctx) + } +} + // ParseInstanceRelation parses the source and destination service instance id or service instance name, // and converts the present one to the missing one respectively. // See flags.InstanceRelationFlags. @@ -72,26 +86,85 @@ func parseInstance(required bool, idFlagName, nameFlagName, serviceIDFlagName st return nil } - if id != "" { - parts := strings.Split(id, "_") - if len(parts) != 2 { - return fmt.Errorf("invalid instance id, cannot be splitted into 2 parts. %v", id) + id, name, err := encode(serviceID, nameFlagName, id, name) + if err != nil { + return err + } + + if err := ctx.Set(idFlagName, id); err != nil { + return err + } + return ctx.Set(nameFlagName, name) + } +} + +func parseInstanceList(required bool, idListFlagName, nameListFlagName, serviceIDFlagName string) func(*cli.Context) error { + return func(ctx *cli.Context) error { + idsArg := ctx.String(idListFlagName) + namesArgs := ctx.String(nameListFlagName) + serviceID := ctx.String(serviceIDFlagName) + + if idsArg == "" && namesArgs == "" { + if required { + return fmt.Errorf(`either flags "--%s" or "--%s" must be given`, idListFlagName, nameListFlagName) + } + return nil + } + + ids := strings.Split(idsArg, ",") + names := strings.Split(namesArgs, ",") + var sliceSize int + if l := len(ids); idsArg != "" && l != 0 { + sliceSize = l + } else { + sliceSize = len(names) + } + instanceIDSlice := make([]string, sliceSize) + instanceNameSlice := make([]string, sliceSize) + for i := 0; i < sliceSize; i++ { + id := "" + name := "" + if len(ids) > i { + id = ids[i] + } + if len(names) > i { + name = names[i] } - s, err := base64.StdEncoding.DecodeString(parts[1]) + + id, name, err := encode(serviceID, nameListFlagName, id, name) if err != nil { return err } - name = string(s) - } else if name != "" { - if serviceID == "" { - return fmt.Errorf(`"--%s" is specified but its related service name or id is not given`, nameFlagName) - } - id = serviceID + "_" + b64enc(name) + + instanceIDSlice[i] = id + instanceNameSlice[i] = name } - if err := ctx.Set(idFlagName, id); err != nil { + instanceIDSliceString := strings.Join(instanceIDSlice, ",") + instanceNameSliceString := strings.Join(instanceNameSlice, ",") + if err := ctx.Set(idListFlagName, instanceIDSliceString); err != nil { return err } - return ctx.Set(nameFlagName, name) + return ctx.Set(nameListFlagName, instanceNameSliceString) + } +} + +func encode(serviceID, nameFlagName, id, name string) (encodedID, encodedName string, err error) { + if id != "" { + parts := strings.Split(id, "_") + if len(parts) != 2 { + return "", "", fmt.Errorf("invalid instance id, cannot be splitted into 2 parts. %v", id) + } + s, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return "", "", err + } + name = string(s) + } else if name != "" { + if serviceID == "" { + return "", "", fmt.Errorf(`"--%s" is specified but its related service name or id is not given`, nameFlagName) + } + id = serviceID + "_" + b64enc(name) } + return id, name, nil } diff --git a/internal/commands/profiling/asyncprofiler/asyncprofiler.go b/internal/commands/profiling/asyncprofiler/asyncprofiler.go new file mode 100644 index 0000000..e68086c --- /dev/null +++ b/internal/commands/profiling/asyncprofiler/asyncprofiler.go @@ -0,0 +1,34 @@ +// 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 asyncprofiler + +import "github.com/urfave/cli/v2" + +var Command = &cli.Command{ + Name: "async", + Usage: "async profiler related sub-command", + UsageText: `If your endpoint has performance issue and could not use tracing to find out what's happening, +you could try it. You could get more information +on https://skywalking.apache.org/docs/main/next/en/concepts-and-designs/profiling.`, + Subcommands: []*cli.Command{ + createCommand, + getTaskListCommand, + getTaskProgressCommand, + analysisCommand, + }, +} diff --git a/internal/commands/profiling/asyncprofiler/create.go b/internal/commands/profiling/asyncprofiler/create.go new file mode 100644 index 0000000..1d21938 --- /dev/null +++ b/internal/commands/profiling/asyncprofiler/create.go @@ -0,0 +1,95 @@ +// 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 asyncprofiler + +import ( + "strings" + + "github.com/urfave/cli/v2" + "skywalking.apache.org/repo/goapi/query" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/internal/model/asyncprofiler" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var createCommand = &cli.Command{ + Name: "create", + Aliases: []string{"c"}, + Usage: "Create a new async profiler task", + UsageText: `Create a new async profiler task + +Examples: +1. Create async-profiler task +$ swctl profiling async create --service-name=service-name --duration=60 --events=cpu,alloc \ + --instance-name-list=instance-name1,instance-name2 --exec-args=interval=50ms`, + Flags: flags.Flags( + flags.ServiceFlags, + flags.InstanceListFlags, + []cli.Flag{ + &cli.IntFlag{ + Name: "duration", + Usage: "task continuous time(second).", + Required: true, + }, + &cli.GenericFlag{ + Name: "events", + Usage: "which event types this task needs to collect.", + Required: true, + Value: &asyncprofiler.ProfilerEventTypeEnumValue{ + Enum: query.AllAsyncProfilerEventType, + }, + }, + &cli.StringFlag{ + Name: "exec-args", + Usage: "other async-profiler execution options, e.g. alloc=2k,lock=2s.", + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.ParseInstanceList(true), + ), + Action: func(ctx *cli.Context) error { + serviceID := ctx.String("service-id") + instanceIds := strings.Split(ctx.String("instance-id-list"), ",") + duration := ctx.Int("duration") + eventTypes := ctx.Generic("events").(*asyncprofiler.ProfilerEventTypeEnumValue).Selected + + var execArgs *string + if args := ctx.String("exec-args"); args != "" { + execArgs = &args + } + + request := &query.AsyncProfilerTaskCreationRequest{ + ServiceID: serviceID, + ServiceInstanceIds: instanceIds, + Duration: duration, + Events: eventTypes, + ExecArgs: execArgs, + } + task, err := profiling.CreateAsyncProfilerTask(ctx, request) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: task, Condition: request}) + }, +} diff --git a/internal/commands/profiling/asyncprofiler/getAnalyze.go b/internal/commands/profiling/asyncprofiler/getAnalyze.go new file mode 100644 index 0000000..c1b0387 --- /dev/null +++ b/internal/commands/profiling/asyncprofiler/getAnalyze.go @@ -0,0 +1,85 @@ +// 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 asyncprofiler + +import ( + "strings" + + "github.com/urfave/cli/v2" + "skywalking.apache.org/repo/goapi/query" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/internal/model/asyncprofiler" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var analysisCommand = &cli.Command{ + Name: "analysis", + Aliases: []string{"a"}, + Usage: "Query async-profiler analysis", + UsageText: `Query async-profiler analysis + +Examples: +1. Query the flame graph produced by async-profiler +$ swctl profiling async analysis --service-name=service-name --task-id=task-id \ + --instance-name-list=instance-name1,instance-name2 --event=execution_sample`, + Flags: flags.Flags( + flags.ServiceFlags, + flags.InstanceListFlags, + []cli.Flag{ + &cli.StringFlag{ + Name: "task-id", + Usage: "async-profiler task id", + Required: true, + }, + &cli.GenericFlag{ + Name: "event", + Usage: "which event types this task needs to collect.", + Required: true, + Value: &asyncprofiler.JFREventTypeEnumValue{ + Enum: query.AllJFREventType, + }, + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.ParseInstanceList(true), + ), + Action: func(ctx *cli.Context) error { + taskID := ctx.String("task-id") + instances := strings.Split(ctx.String("instance-id-list"), ",") + eventType := ctx.Generic("event").(*asyncprofiler.JFREventTypeEnumValue).Selected + + request := &query.AsyncProfilerAnalyzationRequest{ + TaskID: taskID, + InstanceIds: instances, + EventType: eventType, + } + + analyze, err := profiling.GetAsyncProfilerAnalyze(ctx, request) + + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: analyze, Condition: request}) + }, +} diff --git a/internal/commands/profiling/asyncprofiler/getTaskList.go b/internal/commands/profiling/asyncprofiler/getTaskList.go new file mode 100644 index 0000000..067a3e2 --- /dev/null +++ b/internal/commands/profiling/asyncprofiler/getTaskList.go @@ -0,0 +1,90 @@ +// 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 asyncprofiler + +import ( + "github.com/urfave/cli/v2" + "skywalking.apache.org/repo/goapi/query" + + "github.com/apache/skywalking-cli/internal/commands/interceptor" + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var getTaskListCommand = &cli.Command{ + Name: "list", + Aliases: []string{"l"}, + Usage: "Query async-profiler task list", + UsageText: `Query async-profiler task list + +Examples: +1. Query all async-profiler tasks +$ swctl profiling async list --service-name=service-name`, + Flags: flags.Flags( + flags.ServiceFlags, + []cli.Flag{ + &cli.Int64Flag{ + Name: "start-time", + Usage: "The start time (in milliseconds) of the event, measured between the current time and midnight, January 1, 1970 UTC.", + }, + &cli.Int64Flag{ + Name: "end-time", + Usage: "The end time (in milliseconds) of the event, measured between the current time and midnight, January 1, 1970 UTC.", + }, + &cli.IntFlag{ + Name: "limit", + Usage: "Limit defines the number of the tasks to be returned.", + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.ParseService(true), + ), + Action: func(ctx *cli.Context) error { + serviceID := ctx.String("service-id") + var startTime *int64 + if startTimeArg := ctx.Int64("start-time"); startTimeArg != 0 { + startTime = &startTimeArg + } + var endTime *int64 + if endTimeArg := ctx.Int64("end-time"); endTimeArg != 0 { + endTime = &endTimeArg + } + var limit *int + if limitArg := ctx.Int("limit"); limitArg != 0 { + limit = &limitArg + } + + request := &query.AsyncProfilerTaskListRequest{ + ServiceID: serviceID, + StartTime: startTime, + EndTime: endTime, + Limit: limit, + } + + tasks, err := profiling.GetAsyncProfilerTaskList(ctx, request) + + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: tasks, Condition: request}) + }, +} diff --git a/internal/commands/profiling/asyncprofiler/getTaskProgress.go b/internal/commands/profiling/asyncprofiler/getTaskProgress.go new file mode 100644 index 0000000..3943e84 --- /dev/null +++ b/internal/commands/profiling/asyncprofiler/getTaskProgress.go @@ -0,0 +1,55 @@ +// 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 asyncprofiler + +import ( + "github.com/urfave/cli/v2" + + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var getTaskProgressCommand = &cli.Command{ + Name: "progress", + Aliases: []string{"p"}, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "task-id", + Usage: "async profiler task id.", + Required: true, + }, + }, + Usage: "Query async-profiler task progress", + UsageText: `Query async-profiler task progress + +Examples: +1. Query task progress, including task logs and successInstances and errorInstances +$ swctl profiling async progress --task-id=task-id`, + Action: func(ctx *cli.Context) error { + taskID := ctx.String("task-id") + + data, err := profiling.GetAsyncProfilerTaskProgress(ctx, taskID) + + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: data, Condition: taskID}) + }, +} diff --git a/internal/commands/profiling/profiling.go b/internal/commands/profiling/profiling.go index 6059b76..54144b6 100644 --- a/internal/commands/profiling/profiling.go +++ b/internal/commands/profiling/profiling.go @@ -20,6 +20,7 @@ package profiling import ( "github.com/urfave/cli/v2" + "github.com/apache/skywalking-cli/internal/commands/profiling/asyncprofiler" "github.com/apache/skywalking-cli/internal/commands/profiling/continuous" "github.com/apache/skywalking-cli/internal/commands/profiling/ebpf" "github.com/apache/skywalking-cli/internal/commands/profiling/trace" @@ -34,5 +35,6 @@ Please following sub-command to get more information.`, trace.Command, ebpf.Command, continuous.Command, + asyncprofiler.Command, }, } diff --git a/internal/flags/instance.go b/internal/flags/instance.go index 0a5aa44..af3b254 100644 --- a/internal/flags/instance.go +++ b/internal/flags/instance.go @@ -52,3 +52,18 @@ var InstanceRelationFlags = append( Required: false, }, ) + +// InstanceListFlags take either service instance id list or service instance name list as input, +// and transform to the other one. +var InstanceListFlags = []cli.Flag{ + &cli.StringFlag{ + Name: "instance-id-list", + Usage: "`instance id list`, if you don't have instance id list, use `--instances-name` instead", + Required: false, + }, + &cli.StringFlag{ + Name: "instance-name-list", + Usage: "`instance name list`, if you already have instance id list, prefer to use `--instances-id`", + Required: false, + }, +} diff --git a/internal/model/asyncprofiler/asyncProfilerEventType.go b/internal/model/asyncprofiler/asyncProfilerEventType.go new file mode 100644 index 0000000..da3db0c --- /dev/null +++ b/internal/model/asyncprofiler/asyncProfilerEventType.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 asyncprofiler + +import ( + "fmt" + "strings" + + api "skywalking.apache.org/repo/goapi/query" +) + +type ProfilerEventTypeEnumValue struct { + Enum []api.AsyncProfilerEventType + Default []api.AsyncProfilerEventType + Selected []api.AsyncProfilerEventType +} + +func (e *ProfilerEventTypeEnumValue) Set(value string) error { + values := strings.Split(value, ",") + types := make([]api.AsyncProfilerEventType, 0) + for _, v := range values { + for _, enum := range e.Enum { + if strings.EqualFold(enum.String(), v) { + types = append(types, enum) + break + } + } + } + + if len(types) != 0 { + e.Selected = types + return nil + } + + orders := make([]string, len(api.AllAsyncProfilerEventType)) + for i, order := range api.AllAsyncProfilerEventType { + orders[i] = order.String() + } + return fmt.Errorf("allowed analysis aggregate type are %s", strings.Join(orders, ", ")) +} + +func (e *ProfilerEventTypeEnumValue) String() string { + selected := make([]string, len(e.Selected)) + for i, item := range e.Selected { + selected[i] = item.String() + } + return strings.Join(selected, ",") +} diff --git a/internal/model/asyncprofiler/jfrEventType.go b/internal/model/asyncprofiler/jfrEventType.go new file mode 100644 index 0000000..a216872 --- /dev/null +++ b/internal/model/asyncprofiler/jfrEventType.go @@ -0,0 +1,49 @@ +// 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 asyncprofiler + +import ( + "fmt" + "strings" + + api "skywalking.apache.org/repo/goapi/query" +) + +type JFREventTypeEnumValue struct { + Enum []api.JFREventType + Default api.JFREventType + Selected api.JFREventType +} + +func (e *JFREventTypeEnumValue) Set(value string) error { + for _, enum := range e.Enum { + if strings.EqualFold(enum.String(), value) { + e.Selected = enum + return nil + } + } + orders := make([]string, len(api.AllJFREventType)) + for i, order := range api.AllJFREventType { + orders[i] = order.String() + } + return fmt.Errorf("allowed analysis aggregate type are %s", strings.Join(orders, ", ")) +} + +func (e *JFREventTypeEnumValue) String() string { + return e.Selected.String() +} diff --git a/pkg/graphql/profiling/asyncprofiler.go b/pkg/graphql/profiling/asyncprofiler.go new file mode 100644 index 0000000..5db697f --- /dev/null +++ b/pkg/graphql/profiling/asyncprofiler.go @@ -0,0 +1,71 @@ +// 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 profiling + +import ( + "github.com/machinebox/graphql" + "github.com/urfave/cli/v2" + api "skywalking.apache.org/repo/goapi/query" + + "github.com/apache/skywalking-cli/assets" + "github.com/apache/skywalking-cli/pkg/graphql/client" +) + +func CreateAsyncProfilerTask(ctx *cli.Context, condition *api.AsyncProfilerTaskCreationRequest) (api.AsyncProfilerTaskCreationResult, error) { + var response map[string]api.AsyncProfilerTaskCreationResult + + request := graphql.NewRequest(assets.Read("graphqls/profiling/asyncprofiler/CreateTask.graphql")) + request.Var("condition", condition) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func GetAsyncProfilerTaskList(ctx *cli.Context, condition *api.AsyncProfilerTaskListRequest) (api.AsyncProfilerTaskListResult, error) { + var response map[string]api.AsyncProfilerTaskListResult + + request := graphql.NewRequest(assets.Read("graphqls/profiling/asyncprofiler/GetTaskList.graphql")) + request.Var("condition", condition) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func GetAsyncProfilerTaskProgress(ctx *cli.Context, taskID string) (api.AsyncProfilerTaskProgress, error) { + var response map[string]api.AsyncProfilerTaskProgress + + request := graphql.NewRequest(assets.Read("graphqls/profiling/asyncprofiler/GetTaskProgress.graphql")) + request.Var("taskId", taskID) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func GetAsyncProfilerAnalyze(ctx *cli.Context, condition *api.AsyncProfilerAnalyzationRequest) (api.AsyncProfilerAnalyzation, error) { + var response map[string]api.AsyncProfilerAnalyzation + + request := graphql.NewRequest(assets.Read("graphqls/profiling/asyncprofiler/GetAnalysis.graphql")) + request.Var("condition", condition) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} diff --git a/test/cases/basic/expected/dependency-endpoint.yml b/test/cases/basic/expected/dependency-endpoint.yml index b8d11d0..e2d8453 100644 --- a/test/cases/basic/expected/dependency-endpoint.yml +++ b/test/cases/basic/expected/dependency-endpoint.yml @@ -28,6 +28,7 @@ nodes: serviceid: {{ b64enc "consumer" }}.1 servicename: consumer {{- end }} +debuggingtrace: null calls: {{- contains .calls }} - source: {{ b64enc "consumer" }}.1_{{ b64enc "/users" }} diff --git a/test/cases/basic/expected/dependency-instance.yml b/test/cases/basic/expected/dependency-instance.yml index bfa7f05..33dd279 100644 --- a/test/cases/basic/expected/dependency-instance.yml +++ b/test/cases/basic/expected/dependency-instance.yml @@ -41,3 +41,4 @@ calls: - CLIENT {{- end }} {{- end }} +debuggingtrace: null diff --git a/test/cases/basic/expected/dependency-service.yml b/test/cases/basic/expected/dependency-service.yml index 6bf530f..41c789e 100644 --- a/test/cases/basic/expected/dependency-service.yml +++ b/test/cases/basic/expected/dependency-service.yml @@ -41,3 +41,4 @@ calls: - CLIENT - SERVER {{- end }} +debuggingtrace: null \ No newline at end of file diff --git a/test/cases/basic/expected/trace-users-detail.yml b/test/cases/basic/expected/trace-users-detail.yml index ab8b888..a286162 100644 --- a/test/cases/basic/expected/trace-users-detail.yml +++ b/test/cases/basic/expected/trace-users-detail.yml @@ -100,3 +100,4 @@ spans: logs: [ ] attachedevents: [] {{- end }} +debuggingtrace: null \ No newline at end of file diff --git a/test/cases/basic/expected/traces-list.yml b/test/cases/basic/expected/traces-list.yml index 3150040..a51cb7f 100644 --- a/test/cases/basic/expected/traces-list.yml +++ b/test/cases/basic/expected/traces-list.yml @@ -24,3 +24,4 @@ traces: traceids: - {{ index .traceids 0 }} {{- end }} +debuggingtrace: null \ No newline at end of file