From 60cee4a926a867a7bd934b7f94c9a8517e141608 Mon Sep 17 00:00:00 2001 From: mrproliu <741550557@qq.com> Date: Thu, 24 Mar 2022 20:50:09 +0800 Subject: [PATCH] Support eBPF profiling related commands (#138) --- .licenserc.yaml | 1 + CHANGES.md | 3 + LICENSE | 8 + .../CreateEBPFProfilingFixedTimeTask.graphql | 24 ++ .../QueryEBPFProfilingScheduleList.graphql | 40 +++ .../ebpf/QueryEBPFProfilingTaskList.graphql | 34 +++ .../ebpf/QueryEBPFProflingAnalyzation.graphql | 31 +++ .../trace}/CreateTask.graphql | 0 .../trace}/GetProfileAnalyze.graphql | 0 .../trace}/GetProfileTaskLogs.graphql | 0 .../trace}/GetProfiledSegment.graphql | 0 .../trace}/GetTaskList.graphql | 0 .../trace}/GetTaskSegmentList.graphql | 0 cmd/swctl/main.go | 5 +- go.mod | 2 +- go.sum | 4 +- internal/commands/profiling/ebpf/analysis.go | 86 +++++++ .../commands/profiling/ebpf/create/create.go | 28 +++ .../ebpf/create/createByFixedTime.go | 103 ++++++++ internal/commands/profiling/ebpf/ebpf.go | 35 +++ internal/commands/profiling/ebpf/list.go | 85 +++++++ internal/commands/profiling/ebpf/schedules.go | 73 ++++++ internal/commands/profiling/profiling.go | 36 +++ .../{profile => profiling/trace}/create.go | 14 +- .../trace}/getProfileAnalyze.go | 8 +- .../trace}/getProfiledSegment.go | 8 +- .../trace}/getTaskList.go | 18 +- .../trace}/getTaskLogs.go | 14 +- .../trace}/getTaskSegmentList.go | 12 +- .../profile.go => profiling/trace/trace.go} | 6 +- internal/model/ebpf/profilingProcessFinder.go | 52 ++++ internal/model/ebpf/profilingTargetType.go | 52 ++++ pkg/display/display.go | 2 + pkg/display/graph/flamegraph/adapter.go | 75 ++++++ pkg/display/graph/flamegraph/flamegraph.go | 173 +++++++++++++ pkg/display/graph/flamegraph/flamegraph.html | 230 ++++++++++++++++++ pkg/display/graph/flamegraph/render.go | 66 +++++ pkg/display/graph/graph.go | 40 +-- pkg/graphql/profiling/ebpf.go | 79 ++++++ .../profile.go => profiling/trace.go} | 26 +- 40 files changed, 1401 insertions(+), 72 deletions(-) create mode 100644 assets/graphqls/profiling/ebpf/CreateEBPFProfilingFixedTimeTask.graphql create mode 100644 assets/graphqls/profiling/ebpf/QueryEBPFProfilingScheduleList.graphql create mode 100644 assets/graphqls/profiling/ebpf/QueryEBPFProfilingTaskList.graphql create mode 100644 assets/graphqls/profiling/ebpf/QueryEBPFProflingAnalyzation.graphql rename assets/graphqls/{profile => profiling/trace}/CreateTask.graphql (100%) rename assets/graphqls/{profile => profiling/trace}/GetProfileAnalyze.graphql (100%) rename assets/graphqls/{profile => profiling/trace}/GetProfileTaskLogs.graphql (100%) rename assets/graphqls/{profile => profiling/trace}/GetProfiledSegment.graphql (100%) rename assets/graphqls/{profile => profiling/trace}/GetTaskList.graphql (100%) rename assets/graphqls/{profile => profiling/trace}/GetTaskSegmentList.graphql (100%) create mode 100644 internal/commands/profiling/ebpf/analysis.go create mode 100644 internal/commands/profiling/ebpf/create/create.go create mode 100644 internal/commands/profiling/ebpf/create/createByFixedTime.go create mode 100644 internal/commands/profiling/ebpf/ebpf.go create mode 100644 internal/commands/profiling/ebpf/list.go create mode 100644 internal/commands/profiling/ebpf/schedules.go create mode 100644 internal/commands/profiling/profiling.go rename internal/commands/{profile => profiling/trace}/create.go (87%) rename internal/commands/{profile => profiling/trace}/getProfileAnalyze.go (92%) rename internal/commands/{profile => profiling/trace}/getProfiledSegment.go (88%) rename internal/commands/{profile => profiling/trace}/getTaskList.go (73%) rename internal/commands/{profile => profiling/trace}/getTaskLogs.go (80%) rename internal/commands/{profile => profiling/trace}/getTaskSegmentList.go (80%) rename internal/commands/{profile/profile.go => profiling/trace/trace.go} (94%) create mode 100644 internal/model/ebpf/profilingProcessFinder.go create mode 100644 internal/model/ebpf/profilingTargetType.go create mode 100644 pkg/display/graph/flamegraph/adapter.go create mode 100644 pkg/display/graph/flamegraph/flamegraph.go create mode 100644 pkg/display/graph/flamegraph/flamegraph.html create mode 100644 pkg/display/graph/flamegraph/render.go create mode 100644 pkg/graphql/profiling/ebpf.go rename pkg/graphql/{profile/profile.go => profiling/trace.go} (62%) diff --git a/.licenserc.yaml b/.licenserc.yaml index 1048e293..89f1803d 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -31,5 +31,6 @@ header: - '.gitignore' - 'go.mod' - 'go.sum' + - 'pkg/display/graph/flamegraph/flamegraph.html' comment: on-failure diff --git a/CHANGES.md b/CHANGES.md index 174c9455..dc241804 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,9 @@ Release Notes. - Change the GraphQL method to the v9 version according to the server version.(#134) - Add `normal` field to Service entity.(#136) - Add the command `process` for query Process metadata.(#137) +- Add the command `profiling ebpf` for process ebpf profiling.(#138) +- [Breaking Change] Update the command `profile` as a sub-command `profiling trace`, and update `profiled-analyze` command to `analysis`.(#138) +- `profiling ebpf/trace analysis` generates the profiling graph HTML on default and saves it to the current work directory.(#138) ### Bug Fixes diff --git a/LICENSE b/LICENSE index d346d1fb..5a80c2b5 100644 --- a/LICENSE +++ b/LICENSE @@ -200,3 +200,11 @@ See the License for the specific language governing permissions and limitations under the License. +======================================================================== +Apache 2.0 licenses +======================================================================== + +The following components are provided under the Apache License. See project link for details. +The text of each license is the standard Apache 2.0 license. + + pkg/display/graph/flamegraph files from jvm-profiling-tools/async-profiler: https://github.com/jvm-profiling-tools/async-profiler Apache-2.0 \ No newline at end of file diff --git a/assets/graphqls/profiling/ebpf/CreateEBPFProfilingFixedTimeTask.graphql b/assets/graphqls/profiling/ebpf/CreateEBPFProfilingFixedTimeTask.graphql new file mode 100644 index 00000000..777b7d63 --- /dev/null +++ b/assets/graphqls/profiling/ebpf/CreateEBPFProfilingFixedTimeTask.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 ($request: EBPFProfilingTaskFixedTimeCreationRequest!){ + result: createEBPFProfilingFixedTimeTask(request: $request) { + status + id + errorReason + } +} diff --git a/assets/graphqls/profiling/ebpf/QueryEBPFProfilingScheduleList.graphql b/assets/graphqls/profiling/ebpf/QueryEBPFProfilingScheduleList.graphql new file mode 100644 index 00000000..863107d6 --- /dev/null +++ b/assets/graphqls/profiling/ebpf/QueryEBPFProfilingScheduleList.graphql @@ -0,0 +1,40 @@ +# 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: ID!, $duration: Duration!){ + result: queryEBPFProfilingSchedules(taskId: $taskID, duration: $duration) { + scheduleId + taskId + startTime + endTime + process { + id + name + serviceId + serviceName + instanceId + instanceName + layer + agentId + detectType + attributes { + name + value + } + } + } +} \ No newline at end of file diff --git a/assets/graphqls/profiling/ebpf/QueryEBPFProfilingTaskList.graphql b/assets/graphqls/profiling/ebpf/QueryEBPFProfilingTaskList.graphql new file mode 100644 index 00000000..bdcfa6e4 --- /dev/null +++ b/assets/graphqls/profiling/ebpf/QueryEBPFProfilingTaskList.graphql @@ -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. + +query ($query: EBPFProfilingTaskCondition){ + result: queryEBPFProfilingTasks(query: $query) { + taskId + processFinderType + serviceId + serviceName + instanceId + instanceName + processId + processName + taskStartTime + triggerType + fixedTriggerDuration + targetType + createTime + } +} \ No newline at end of file diff --git a/assets/graphqls/profiling/ebpf/QueryEBPFProflingAnalyzation.graphql b/assets/graphqls/profiling/ebpf/QueryEBPFProflingAnalyzation.graphql new file mode 100644 index 00000000..d4f9f757 --- /dev/null +++ b/assets/graphqls/profiling/ebpf/QueryEBPFProflingAnalyzation.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 ($taskID: ID!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!){ + result: getEBPFProfilingAnalyzation(taskId: $taskID, timeRanges: $timeRanges) { + tip + trees { + elements { + id + parentId + symbol + stackType + dumpCount + } + } + } +} \ No newline at end of file diff --git a/assets/graphqls/profile/CreateTask.graphql b/assets/graphqls/profiling/trace/CreateTask.graphql similarity index 100% rename from assets/graphqls/profile/CreateTask.graphql rename to assets/graphqls/profiling/trace/CreateTask.graphql diff --git a/assets/graphqls/profile/GetProfileAnalyze.graphql b/assets/graphqls/profiling/trace/GetProfileAnalyze.graphql similarity index 100% rename from assets/graphqls/profile/GetProfileAnalyze.graphql rename to assets/graphqls/profiling/trace/GetProfileAnalyze.graphql diff --git a/assets/graphqls/profile/GetProfileTaskLogs.graphql b/assets/graphqls/profiling/trace/GetProfileTaskLogs.graphql similarity index 100% rename from assets/graphqls/profile/GetProfileTaskLogs.graphql rename to assets/graphqls/profiling/trace/GetProfileTaskLogs.graphql diff --git a/assets/graphqls/profile/GetProfiledSegment.graphql b/assets/graphqls/profiling/trace/GetProfiledSegment.graphql similarity index 100% rename from assets/graphqls/profile/GetProfiledSegment.graphql rename to assets/graphqls/profiling/trace/GetProfiledSegment.graphql diff --git a/assets/graphqls/profile/GetTaskList.graphql b/assets/graphqls/profiling/trace/GetTaskList.graphql similarity index 100% rename from assets/graphqls/profile/GetTaskList.graphql rename to assets/graphqls/profiling/trace/GetTaskList.graphql diff --git a/assets/graphqls/profile/GetTaskSegmentList.graphql b/assets/graphqls/profiling/trace/GetTaskSegmentList.graphql similarity index 100% rename from assets/graphqls/profile/GetTaskSegmentList.graphql rename to assets/graphqls/profiling/trace/GetTaskSegmentList.graphql diff --git a/cmd/swctl/main.go b/cmd/swctl/main.go index 4944a273..58aa2d72 100644 --- a/cmd/swctl/main.go +++ b/cmd/swctl/main.go @@ -18,6 +18,7 @@ package main import ( + _ "embed" "os" "runtime" @@ -36,7 +37,7 @@ import ( "github.com/apache/skywalking-cli/internal/commands/logs" "github.com/apache/skywalking-cli/internal/commands/metrics" "github.com/apache/skywalking-cli/internal/commands/process" - "github.com/apache/skywalking-cli/internal/commands/profile" + "github.com/apache/skywalking-cli/internal/commands/profiling" "github.com/apache/skywalking-cli/internal/commands/service" "github.com/apache/skywalking-cli/internal/commands/trace" "github.com/apache/skywalking-cli/internal/logger" @@ -96,12 +97,12 @@ services, service instances, etc.` install.Command, event.Command, logs.Command, - profile.Command, completion.Command, dependency.Command, alarm.Command, layer.Command, process.Command, + profiling.Command, } app.Before = interceptor.BeforeChain( diff --git a/go.mod b/go.mod index 977b1d6b..a3743a77 100644 --- a/go.mod +++ b/go.mod @@ -20,5 +20,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 k8s.io/apimachinery v0.21.1 sigs.k8s.io/controller-runtime v0.7.0 - skywalking.apache.org/repo/goapi v0.0.0-20220310042848-795a2b3fcdfe + skywalking.apache.org/repo/goapi v0.0.0-20220322033350-0661327d31e3 ) diff --git a/go.sum b/go.sum index bf3baf94..1188c127 100644 --- a/go.sum +++ b/go.sum @@ -941,5 +941,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 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-20220310042848-795a2b3fcdfe h1:Txa1/FZfiA++l6gEC843VMhM4nMGatag/mzk1j4dhWw= -skywalking.apache.org/repo/goapi v0.0.0-20220310042848-795a2b3fcdfe/go.mod h1:wzVLZ8F88Idy5tlmcGgJCH2NY8GUWxJ2RuNVXkao+SM= +skywalking.apache.org/repo/goapi v0.0.0-20220322033350-0661327d31e3 h1:Y6uXXJh8R6X1b4HNpyBG0yc78mdmCxQA6cTsGZJ3F4o= +skywalking.apache.org/repo/goapi v0.0.0-20220322033350-0661327d31e3/go.mod h1:wzVLZ8F88Idy5tlmcGgJCH2NY8GUWxJ2RuNVXkao+SM= diff --git a/internal/commands/profiling/ebpf/analysis.go b/internal/commands/profiling/ebpf/analysis.go new file mode 100644 index 00000000..1e47018f --- /dev/null +++ b/internal/commands/profiling/ebpf/analysis.go @@ -0,0 +1,86 @@ +// 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 ebpf + +import ( + "strconv" + "strings" + + api "skywalking.apache.org/repo/goapi/query" + + "github.com/urfave/cli/v2" + + "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 AnalyzationCommand = &cli.Command{ + Name: "analysis", + Aliases: []string{"as"}, + Usage: `analyze ebpf profiling task`, + UsageText: `This command analysis profiling task, via id of task and time ranges. + +Example: +1. Analysis profiling tasks of task id "abc" and time range in 1648020042869 to 1648020100764. +$ swctl profiling ebpf analysis --task-id=abc --time-ranges=1648020042869-1648020100764 +`, + Flags: flags.Flags( + flags.DurationFlags, + []cli.Flag{ + &cli.StringFlag{ + Name: "task-id", + Usage: "the `task-id` by which task are scheduled", + Required: true, + }, + &cli.StringFlag{ + Name: "time-ranges", + Usage: "need to analyze time ranges in the segment: start-end,start-end", + }, + }, + ), + Action: func(ctx *cli.Context) error { + taskID := ctx.String("task-id") + + timeRangeStr := ctx.String("time-ranges") + var timeRanges []*api.EBPFProfilingAnalyzeTimeRange = nil + if timeRangeStr != "" { + tagArr := strings.Split(timeRangeStr, ",") + for _, tag := range tagArr { + kv := strings.Split(tag, "-") + start, err := strconv.ParseInt(kv[0], 10, 64) + if err != nil { + return err + } + end, err := strconv.ParseInt(kv[1], 10, 64) + if err != nil { + return err + } + timeRanges = append(timeRanges, &api.EBPFProfilingAnalyzeTimeRange{Start: start, End: end}) + } + } + + analyzation, err := profiling.QueryEBPFProfilingAnalyzation(ctx, taskID, timeRanges) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: analyzation}) + }, +} diff --git a/internal/commands/profiling/ebpf/create/create.go b/internal/commands/profiling/ebpf/create/create.go new file mode 100644 index 00000000..1c85ce2e --- /dev/null +++ b/internal/commands/profiling/ebpf/create/create.go @@ -0,0 +1,28 @@ +// 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 create + +import "github.com/urfave/cli/v2" + +var CreateCommand = &cli.Command{ + Name: "create", + Usage: "eBPF Profiling task create related sub-command", + Subcommands: []*cli.Command{ + FixedTimeCreateCommand, + }, +} diff --git a/internal/commands/profiling/ebpf/create/createByFixedTime.go b/internal/commands/profiling/ebpf/create/createByFixedTime.go new file mode 100644 index 00000000..a240b2f3 --- /dev/null +++ b/internal/commands/profiling/ebpf/create/createByFixedTime.go @@ -0,0 +1,103 @@ +// 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 create + +import ( + "time" + + "github.com/apache/skywalking-cli/internal/flags" + "github.com/apache/skywalking-cli/internal/model/ebpf" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" + + "github.com/urfave/cli/v2" + + api "skywalking.apache.org/repo/goapi/query" +) + +var FixedTimeCreateCommand = &cli.Command{ + Name: "fixed", + Aliases: []string{"cft"}, + Usage: "Create a new ebpf profiling fixed time task", + UsageText: `Create a new ebpf profiling fixed time task + +Examples: +1. Create ebpf profiling fixed time task +$ swctl ebpf-profiling createByFixedTime --process-finder=PROCESS_ID --process-id=abc --duration=1m --target-type=ON_CPU`, + Flags: flags.Flags( + []cli.Flag{ + &cli.GenericFlag{ + Name: "process-finder", + Usage: "the `process-finder` by the way to address the target process", + Value: &ebpf.ProfilingProcessFinderTypeEnumValue{ + Enum: api.AllEBPFProfilingProcessFinderType, + Default: api.EBPFProfilingProcessFinderTypeProcessID, + Selected: api.EBPFProfilingProcessFinderTypeProcessID, + }, + }, + &cli.StringFlag{ + Name: "process-id", + Usage: "the `process-id` by which process ID need to be profiling", + Required: false, + }, + &cli.StringFlag{ + Name: "duration", + Usage: "profiling task continuous time.", + Required: true, + }, + &cli.Int64Flag{ + Name: "start-time", + Usage: "profiling task start time(millisecond).", + }, + &cli.GenericFlag{ + Name: "target-type", + Usage: "the `target-type` by the way of profiling the process", + Value: &ebpf.ProfilingTargetTypeEnumValue{ + Enum: api.AllEBPFProfilingTargetType, + Default: api.EBPFProfilingTargetTypeOnCPU, + Selected: api.EBPFProfilingTargetTypeOnCPU, + }, + }, + }, + ), + Action: func(ctx *cli.Context) error { + processID := ctx.String("process-id") + duration, err := time.ParseDuration(ctx.String("duration")) + if err != nil { + return err + } + request := &api.EBPFProfilingTaskFixedTimeCreationRequest{ + ProcessFinder: &api.EBPFProfilingProcessFinder{ + FinderType: ctx.Generic("process-finder").(*ebpf.ProfilingProcessFinderTypeEnumValue).Selected, + ProcessID: &processID, + }, + StartTime: ctx.Int64("start-time"), + Duration: int(duration.Seconds()), + TargetType: ctx.Generic("target-type").(*ebpf.ProfilingTargetTypeEnumValue).Selected, + } + + task, err := profiling.CreateEBPFProfilingFixedTimeTask(ctx, request) + + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: task, Condition: request}) + }, +} diff --git a/internal/commands/profiling/ebpf/ebpf.go b/internal/commands/profiling/ebpf/ebpf.go new file mode 100644 index 00000000..e9c99268 --- /dev/null +++ b/internal/commands/profiling/ebpf/ebpf.go @@ -0,0 +1,35 @@ +// 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 ebpf + +import ( + "github.com/apache/skywalking-cli/internal/commands/profiling/ebpf/create" + + "github.com/urfave/cli/v2" +) + +var Command = &cli.Command{ + Name: "ebpf", + Usage: "eBPF Profiling related sub-command", + Subcommands: []*cli.Command{ + create.CreateCommand, + ListTaskCommand, + ListScheduleCommand, + AnalyzationCommand, + }, +} diff --git a/internal/commands/profiling/ebpf/list.go b/internal/commands/profiling/ebpf/list.go new file mode 100644 index 00000000..5b8c101f --- /dev/null +++ b/internal/commands/profiling/ebpf/list.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 ebpf + +import ( + "fmt" + + 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/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var ListTaskCommand = &cli.Command{ + Name: "list", + Aliases: []string{"ls"}, + Usage: `query ebpf profiling task list`, + UsageText: `This command lists all ebpf profiling task, via id or name in service, instance or process. + +Example: +1. Query profiling tasks of service "business-zone::projectC" +$ swctl profiling ebpf list --service-name=service-name + +2. Query profiling tasks of instance name "provider-01" and service name "provider": +$ swctl profiling ebpf list --instance-name provider-01 --service-name provider + +3. Query profiling tasks of process id "abc" +$ swctl profiling ebpf list --process-id=abc +`, + Flags: flags.Flags( + flags.ServiceFlags, + flags.InstanceFlags, + []cli.Flag{ + &cli.StringFlag{ + Name: "process-id", + Usage: "the `process-id` by which process ID need to be profiling", + Required: false, + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.ParseService(false), + interceptor.ParseInstance(false), + ), + Action: func(ctx *cli.Context) error { + serviceID := ctx.String("service-id") + instanceID := ctx.String("instance-id") + processID := ctx.String("process-id") + if serviceID == "" && instanceID == "" && processID == "" { + return fmt.Errorf("service, instance, or process must provide one") + } + + processes, err := profiling.QueryEBPFProfilingTaskList(ctx, &api.EBPFProfilingTaskCondition{ + FinderType: nil, + ServiceID: &serviceID, + InstanceID: &instanceID, + ProcessID: &processID, + }) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: processes}) + }, +} diff --git a/internal/commands/profiling/ebpf/schedules.go b/internal/commands/profiling/ebpf/schedules.go new file mode 100644 index 00000000..c3b8caff --- /dev/null +++ b/internal/commands/profiling/ebpf/schedules.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 ebpf + +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/model" + "github.com/apache/skywalking-cli/pkg/display" + "github.com/apache/skywalking-cli/pkg/display/displayable" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" +) + +var ListScheduleCommand = &cli.Command{ + Name: "schedules", + Aliases: []string{"ss"}, + Usage: `query ebpf profiling task schedules`, + UsageText: `This command lists all schedule of the ebpf profiling task, via id of task. + +Example: +1. Query profiling schedules of task id "abc" +$ swctl profiling ebpf schedules --task-id=abc +`, + Flags: flags.Flags( + flags.DurationFlags, + []cli.Flag{ + &cli.StringFlag{ + Name: "task-id", + Usage: "the `task-id` by which task are scheduled", + Required: true, + }, + }, + ), + Before: interceptor.BeforeChain( + interceptor.DurationInterceptor, + ), + Action: func(ctx *cli.Context) error { + taskID := ctx.String("task-id") + start := ctx.String("start") + end := ctx.String("end") + step := ctx.Generic("step") + + schedules, err := profiling.QueryEBPFProfilingScheduleList(ctx, taskID, &api.Duration{ + Start: start, + End: end, + Step: step.(*model.StepEnumValue).Selected, + }) + if err != nil { + return err + } + + return display.Display(ctx, &displayable.Displayable{Data: schedules}) + }, +} diff --git a/internal/commands/profiling/profiling.go b/internal/commands/profiling/profiling.go new file mode 100644 index 00000000..2a6cb5ab --- /dev/null +++ b/internal/commands/profiling/profiling.go @@ -0,0 +1,36 @@ +// 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/urfave/cli/v2" + + "github.com/apache/skywalking-cli/internal/commands/profiling/ebpf" + "github.com/apache/skywalking-cli/internal/commands/profiling/trace" +) + +var Command = &cli.Command{ + Name: "profiling", + Usage: "profiling related sub-command", + UsageText: `If your application has performance issue, you could try to profiling. +Please following sub-command to get more information.`, + Subcommands: []*cli.Command{ + trace.Command, + ebpf.Command, + }, +} diff --git a/internal/commands/profile/create.go b/internal/commands/profiling/trace/create.go similarity index 87% rename from internal/commands/profile/create.go rename to internal/commands/profiling/trace/create.go index 95219c66..75ec402a 100644 --- a/internal/commands/profile/create.go +++ b/internal/commands/profiling/trace/create.go @@ -15,14 +15,14 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "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/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" api "skywalking.apache.org/repo/goapi/query" @@ -32,12 +32,12 @@ import ( var createCommand = &cli.Command{ Name: "create", Aliases: []string{"c"}, - Usage: "Create a new profile task", - UsageText: `Create a new profile task + Usage: "Create a new trace profiling task", + UsageText: `Create a new trace profiling task Examples: -1. Create profile task -$ swctl profile create --service-name=service-name --endpoint=endpoint --start-time=1627656127860 --duration=5 \ +1. Create trace profiling task +$ swctl profiling trace create --service-name=service-name --endpoint=endpoint --start-time=1627656127860 --duration=5 \ --min-duration-threshold=0 --dump-period=10 --max-sampling-count=9`, Flags: flags.Flags( flags.EndpointFlags, @@ -81,7 +81,7 @@ $ swctl profile create --service-name=service-name --endpoint=endpoint --start-t MaxSamplingCount: ctx.Int("max-sampling-count"), } - task, err := profile.CreateTask(ctx, request) + task, err := profiling.CreateTraceTask(ctx, request) if err != nil { return err diff --git a/internal/commands/profile/getProfileAnalyze.go b/internal/commands/profiling/trace/getProfileAnalyze.go similarity index 92% rename from internal/commands/profile/getProfileAnalyze.go rename to internal/commands/profiling/trace/getProfileAnalyze.go index 118096fc..cff937a2 100644 --- a/internal/commands/profile/getProfileAnalyze.go +++ b/internal/commands/profiling/trace/getProfileAnalyze.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "strconv" @@ -23,7 +23,7 @@ import ( "github.com/apache/skywalking-cli/pkg/display" "github.com/apache/skywalking-cli/pkg/display/displayable" - "github.com/apache/skywalking-cli/pkg/graphql/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" "github.com/urfave/cli/v2" @@ -31,7 +31,7 @@ import ( ) var getProfiledAnalyzeCommand = &cli.Command{ - Name: "profiled-analyze", + Name: "analysis", Aliases: []string{"pa"}, Usage: "Analyze profiled segment.", ArgsUsage: "[parameters...]", @@ -66,7 +66,7 @@ var getProfiledAnalyzeCommand = &cli.Command{ } } - analysis, err := profile.GetProfileAnalyze(ctx, segmentID, timeRanges) + analysis, err := profiling.GetTraceProfilingAnalyze(ctx, segmentID, timeRanges) if err != nil { return err diff --git a/internal/commands/profile/getProfiledSegment.go b/internal/commands/profiling/trace/getProfiledSegment.go similarity index 88% rename from internal/commands/profile/getProfiledSegment.go rename to internal/commands/profiling/trace/getProfiledSegment.go index 1525e619..b6a363a4 100644 --- a/internal/commands/profile/getProfiledSegment.go +++ b/internal/commands/profiling/trace/getProfiledSegment.go @@ -15,12 +15,12 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "github.com/apache/skywalking-cli/pkg/display" "github.com/apache/skywalking-cli/pkg/display/displayable" - "github.com/apache/skywalking-cli/pkg/graphql/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" "github.com/urfave/cli/v2" ) @@ -33,7 +33,7 @@ var getProfiledSegmentCommand = &cli.Command{ Examples: 1. Analyze profiled segment with time ranges. -$ swctl profile profiled-segment --segment-id=`, +$ swctl profiling trace profiled-segment --segment-id=`, Flags: []cli.Flag{ &cli.StringFlag{ Name: "segment-id", @@ -42,7 +42,7 @@ $ swctl profile profiled-segment --segment-id=`, }, Action: func(ctx *cli.Context) error { segmentID := ctx.String("segment-id") - segment, err := profile.GetProfiledSegment(ctx, segmentID) + segment, err := profiling.GetTraceProfilingSegment(ctx, segmentID) if err != nil { return err diff --git a/internal/commands/profile/getTaskList.go b/internal/commands/profiling/trace/getTaskList.go similarity index 73% rename from internal/commands/profile/getTaskList.go rename to internal/commands/profiling/trace/getTaskList.go index 8ec80e31..840be061 100644 --- a/internal/commands/profile/getTaskList.go +++ b/internal/commands/profiling/trace/getTaskList.go @@ -15,14 +15,14 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "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/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" "github.com/urfave/cli/v2" ) @@ -30,15 +30,15 @@ import ( var getTaskListCommand = &cli.Command{ Name: "list", Aliases: []string{"l"}, - Usage: "Query profile task list", - UsageText: `Query profile task list + Usage: "Query trace profiling task list", + UsageText: `Query trace profiling task list Examples: -1. Query all profiling tasks -$ swctl profile list --service-name=service-name --endpoint-name=endpoint +1. Query all trace profiling tasks +$ swctl profiling trace list --service-name=service-name --endpoint-name=endpoint -2. Query profiling tasks of service "business-zone::projectC", endpoint "/projectC/{value}" -$ swctl profile list --service-name=business-zone::projectC --endpoint-name=/projectC/{value}`, +2. Query trace profiling tasks of service "business-zone::projectC", endpoint "/projectC/{value}" +$ swctl profiling trace list --service-name=business-zone::projectC --endpoint-name=/projectC/{value}`, Flags: flags.Flags( flags.EndpointFlags, ), @@ -49,7 +49,7 @@ $ swctl profile list --service-name=business-zone::projectC --endpoint-name=/pro serviceID := ctx.String("service-id") endpoint := ctx.String("endpoint-name") - task, err := profile.GetTaskList(ctx, serviceID, endpoint) + task, err := profiling.GetTraceProfilingTaskList(ctx, serviceID, endpoint) if err != nil { return err diff --git a/internal/commands/profile/getTaskLogs.go b/internal/commands/profiling/trace/getTaskLogs.go similarity index 80% rename from internal/commands/profile/getTaskLogs.go rename to internal/commands/profiling/trace/getTaskLogs.go index 2b7d0e61..2c23219b 100644 --- a/internal/commands/profile/getTaskLogs.go +++ b/internal/commands/profiling/trace/getTaskLogs.go @@ -15,12 +15,12 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "github.com/apache/skywalking-cli/pkg/display" "github.com/apache/skywalking-cli/pkg/display/displayable" - "github.com/apache/skywalking-cli/pkg/graphql/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" "github.com/urfave/cli/v2" ) @@ -34,16 +34,16 @@ var getTaskLogListCommand = &cli.Command{ Usage: "profile task id.", }, }, - Usage: "Query profile task log list", - UsageText: `Query profile task log list + Usage: "Query trace profiling task log list", + UsageText: `Query trace profiling task log list Examples: -1. Query all profile logs of task id "task-id" -$ swctl profile logs --task-id=task-id`, +1. Query all trace profiling logs of task id "task-id" +$ swctl profiling trace logs --task-id=task-id`, Action: func(ctx *cli.Context) error { taskID := ctx.String("task-id") - task, err := profile.GetTaskLogList(ctx, taskID) + task, err := profiling.GetTraceProfilingTaskLogList(ctx, taskID) if err != nil { return err diff --git a/internal/commands/profile/getTaskSegmentList.go b/internal/commands/profiling/trace/getTaskSegmentList.go similarity index 80% rename from internal/commands/profile/getTaskSegmentList.go rename to internal/commands/profiling/trace/getTaskSegmentList.go index 2b8b6335..b0ce7e17 100644 --- a/internal/commands/profile/getTaskSegmentList.go +++ b/internal/commands/profiling/trace/getTaskSegmentList.go @@ -15,12 +15,12 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "github.com/apache/skywalking-cli/pkg/display" "github.com/apache/skywalking-cli/pkg/display/displayable" - "github.com/apache/skywalking-cli/pkg/graphql/profile" + "github.com/apache/skywalking-cli/pkg/graphql/profiling" "github.com/urfave/cli/v2" ) @@ -28,12 +28,12 @@ import ( var getTaskSegmentListCommand = &cli.Command{ Name: "segment-list", Aliases: []string{"sl"}, - Usage: "Query profile task segment list", - UsageText: `Query profile task segment list + Usage: "Query profiling trace task segment list", + UsageText: `Query profiling trace task segment list Examples: 1. Query profiled segment list -$ swctl profile segment-list --service-name=service-name --endpoint-name=endpoint`, +$ swctl profiling trace segment-list --service-name=service-name --endpoint-name=endpoint`, Flags: []cli.Flag{ &cli.StringFlag{ Name: "task-id", @@ -42,7 +42,7 @@ $ swctl profile segment-list --service-name=service-name --endpoint-name=endpoin }, Action: func(ctx *cli.Context) error { taskID := ctx.String("task-id") - segmentList, err := profile.GetTaskSegmentList(ctx, taskID) + segmentList, err := profiling.GetTraceProfilingTaskSegmentList(ctx, taskID) if err != nil { return err diff --git a/internal/commands/profile/profile.go b/internal/commands/profiling/trace/trace.go similarity index 94% rename from internal/commands/profile/profile.go rename to internal/commands/profiling/trace/trace.go index feffabde..e26b3085 100644 --- a/internal/commands/profile/profile.go +++ b/internal/commands/profiling/trace/trace.go @@ -15,15 +15,15 @@ // specific language governing permissions and limitations // under the License. -package profile +package trace import ( "github.com/urfave/cli/v2" ) var Command = &cli.Command{ - Name: "profile", - Usage: "Profile related sub-command", + Name: "trace", + Usage: "trace profiling related sub-command", UsageText: `If your endpoint has performance issue and could not use tracing to find out what's happening, you could try to profile. You could get more information on https://github.com/apache/skywalking/blob/master/docs/en/guides/backend-profile.md. diff --git a/internal/model/ebpf/profilingProcessFinder.go b/internal/model/ebpf/profilingProcessFinder.go new file mode 100644 index 00000000..cc6faa8c --- /dev/null +++ b/internal/model/ebpf/profilingProcessFinder.go @@ -0,0 +1,52 @@ +// 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 ebpf + +import ( + "fmt" + "strings" + + api "skywalking.apache.org/repo/goapi/query" +) + +// ProfilingProcessFinderTypeEnumValue defines the values domain of --process-finder option +type ProfilingProcessFinderTypeEnumValue struct { + Enum []api.EBPFProfilingProcessFinderType + Default api.EBPFProfilingProcessFinderType + Selected api.EBPFProfilingProcessFinderType +} + +// Set the --process-finder value, from raw string to ProfilingProcessFinderTypeEnumValue +func (s *ProfilingProcessFinderTypeEnumValue) Set(value string) error { + for _, enum := range s.Enum { + if strings.EqualFold(enum.String(), value) { + s.Selected = enum + return nil + } + } + orders := make([]string, len(api.AllEBPFProfilingProcessFinderType)) + for i, order := range api.AllEBPFProfilingProcessFinderType { + orders[i] = order.String() + } + return fmt.Errorf("allowed process finder are %s", strings.Join(orders, ", ")) +} + +// String representation of the order +func (s ProfilingProcessFinderTypeEnumValue) String() string { + return s.Selected.String() +} diff --git a/internal/model/ebpf/profilingTargetType.go b/internal/model/ebpf/profilingTargetType.go new file mode 100644 index 00000000..0170a29f --- /dev/null +++ b/internal/model/ebpf/profilingTargetType.go @@ -0,0 +1,52 @@ +// 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 ebpf + +import ( + "fmt" + "strings" + + api "skywalking.apache.org/repo/goapi/query" +) + +// ProfilingTargetTypeEnumValue defines the values domain of --process-finder option +type ProfilingTargetTypeEnumValue struct { + Enum []api.EBPFProfilingTargetType + Default api.EBPFProfilingTargetType + Selected api.EBPFProfilingTargetType +} + +// Set the --process-finder value, from raw string to ProfilingTargetTypeEnumValue +func (s *ProfilingTargetTypeEnumValue) Set(value string) error { + for _, enum := range s.Enum { + if strings.EqualFold(enum.String(), value) { + s.Selected = enum + return nil + } + } + orders := make([]string, len(api.AllEBPFProfilingTargetType)) + for i, order := range api.AllEBPFProfilingTargetType { + orders[i] = order.String() + } + return fmt.Errorf("allowed target type are %s", strings.Join(orders, ", ")) +} + +// String representation of the order +func (s ProfilingTargetTypeEnumValue) String() string { + return s.Selected.String() +} diff --git a/pkg/display/display.go b/pkg/display/display.go index 24469239..2004e131 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -49,6 +49,8 @@ var style = map[string]string{"dashboard global": "graph", "service list": "table", "t": "graph", "trace": "graph", + "ebpf analysis": "graph", + "trace analysis": "graph", } // Display the object in the style specified in flag --display diff --git a/pkg/display/graph/flamegraph/adapter.go b/pkg/display/graph/flamegraph/adapter.go new file mode 100644 index 00000000..d12cd640 --- /dev/null +++ b/pkg/display/graph/flamegraph/adapter.go @@ -0,0 +1,75 @@ +// 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 flamegraph + +import api "skywalking.apache.org/repo/goapi/query" + +type eBPFStackElementAdapter struct { + *api.EBPFProfilingStackElement +} + +func (e *eBPFStackElementAdapter) ID() string { + return e.EBPFProfilingStackElement.ID +} + +func (e *eBPFStackElementAdapter) ParentID() string { + return e.EBPFProfilingStackElement.ParentID +} + +func (e *eBPFStackElementAdapter) DumpCount() int64 { + return e.EBPFProfilingStackElement.DumpCount +} + +func (e *eBPFStackElementAdapter) Symbol() string { + return e.EBPFProfilingStackElement.Symbol +} + +func (e *eBPFStackElementAdapter) StackType() StackType { + var stackType StackType + switch e.EBPFProfilingStackElement.StackType { + case api.EBPFProfilingStackTypeKernelSpace: + stackType = StackTypeKernelSpace + case api.EBPFProfilingStackTypeUserSpace: + stackType = StackTypeUserSpace + } + return stackType +} + +type traceStackElementAdapter struct { + *api.ProfileStackElement +} + +func (e *traceStackElementAdapter) ID() string { + return e.ProfileStackElement.ID +} + +func (e *traceStackElementAdapter) ParentID() string { + return e.ProfileStackElement.ParentID +} + +func (e *traceStackElementAdapter) DumpCount() int64 { + return int64(e.ProfileStackElement.Count) +} + +func (e *traceStackElementAdapter) Symbol() string { + return e.ProfileStackElement.CodeSignature +} + +func (e *traceStackElementAdapter) StackType() StackType { + return StackTypeUserSpace +} diff --git a/pkg/display/graph/flamegraph/flamegraph.go b/pkg/display/graph/flamegraph/flamegraph.go new file mode 100644 index 00000000..b4afddf9 --- /dev/null +++ b/pkg/display/graph/flamegraph/flamegraph.go @@ -0,0 +1,173 @@ +// 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 flamegraph + +import ( + "bytes" + "fmt" + "html/template" + "os" + "path/filepath" + "time" + + "github.com/apache/skywalking-cli/internal/logger" + + api "skywalking.apache.org/repo/goapi/query" + + "github.com/urfave/cli/v2" +) + +func DisplayByTrace(ctx *cli.Context, analysis api.ProfileAnalyzation) error { + trees := make([]*ProfilingDataTree, 0) + for _, tree := range analysis.Trees { + elements := make([]ProfilingDataStackElement, 0) + for _, e := range tree.Elements { + elements = append(elements, &traceStackElementAdapter{e}) + } + trees = append(trees, &ProfilingDataTree{Elements: elements}) + } + + return display(ctx, trees) +} + +func DisplayByEBPF(ctx *cli.Context, analysis *api.EBPFProfilingAnalyzation) error { + trees := make([]*ProfilingDataTree, 0) + for _, tree := range analysis.Trees { + elements := make([]ProfilingDataStackElement, 0) + for _, e := range tree.Elements { + elements = append(elements, &eBPFStackElementAdapter{e}) + } + trees = append(trees, &ProfilingDataTree{Elements: elements}) + } + + return display(ctx, trees) +} + +func display(_ *cli.Context, trees []*ProfilingDataTree) error { + if len(trees) == 0 { + return fmt.Errorf("could not find the analysis data") + } + + // generate flame graph file path for write + flameGraphPath, err := generateFlameGraphFile() + if err != nil { + return err + } + + // build data + data := make(map[string]interface{}) + elements, maxDepth := buildFlameGraphElements(trees) + data["elements"] = elements + data["maxDepth"] = maxDepth + 1 + data["canvasHeight"] = maxDepth*16 + 30 + + // render template + return renderFlameGraphAndWrite(flameGraphPath, data) +} + +func renderFlameGraphAndWrite(path string, data map[string]interface{}) error { + // render template + var b bytes.Buffer + tmpl, err := template.New("flameGraphTemplate").Parse(flameGraphHTML) + if err != nil { + return fmt.Errorf("failed to parse flame graph template: %v", err) + } + if err = tmpl.Execute(&b, data); err != nil { + return fmt.Errorf("failed to render flame graph: %v", err) + } + + // write to file + file, err := os.Create(path) + if err != nil { + return fmt.Errorf("could not create the flame graph file, %v", err) + } + _, err = file.Write(b.Bytes()) + if err != nil { + return fmt.Errorf("could not write the flame graph to file, %v", err) + } + logger.Log.Infof("success write the flame graph to: %s", path) + return nil +} + +func generateFlameGraphFile() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("could not get current directory: %v", err) + } + flameGraphPath := filepath.Join(wd, buildFileName()) + return flameGraphPath, nil +} + +func buildFileName() string { + return fmt.Sprintf("flame_graph_%d.html", time.Now().Unix()) +} + +func buildFlameGraphElements(trees []*ProfilingDataTree) (result []*StackElement, maxDepth int64) { + // adding the root element + root := &StackElement{Symbol: "all"} + result = append(result, root) + + // build elements + var left int64 + for _, t := range trees { + result, left = buildFlameGraphChildElements(result, t.Elements, nil, left) + } + + // calculate the root total count + for _, r := range result { + if r.ParentID == "0" { + root.Count += r.Count + } + if maxDepth < r.Depth { + maxDepth = r.Depth + } + } + return result, maxDepth +} + +func buildFlameGraphChildElements(renderElements []*StackElement, dataElements []ProfilingDataStackElement, + parent *StackElement, relativeLeft int64) (result []*StackElement, left int64) { + parentID := "0" + var depth int64 = 1 + left = relativeLeft + if parent != nil { + parentID = parent.ID + depth = parent.Depth + 1 + left += parent.Left + } + es := findProfilingElementByParentID(dataElements, parentID) + for _, element := range es { + current := GenerateStackElementByProfilingData(element, depth, left) + renderElements = append(renderElements, current) + + left += element.DumpCount() + renderElements, _ = buildFlameGraphChildElements(renderElements, dataElements, current, relativeLeft) + } + + return renderElements, left +} + +func findProfilingElementByParentID(elements []ProfilingDataStackElement, parentID string) []ProfilingDataStackElement { + children := make([]ProfilingDataStackElement, 0) + for _, e := range elements { + if e.ParentID() == parentID { + children = append(children, e) + } + } + return children +} diff --git a/pkg/display/graph/flamegraph/flamegraph.html b/pkg/display/graph/flamegraph/flamegraph.html new file mode 100644 index 00000000..7d4e5dc4 --- /dev/null +++ b/pkg/display/graph/flamegraph/flamegraph.html @@ -0,0 +1,230 @@ + + + + + + + + + +
  
+
Produced by Apache SkyWalking
+ +
+

Matched:

+

 

+ \ No newline at end of file diff --git a/pkg/display/graph/flamegraph/render.go b/pkg/display/graph/flamegraph/render.go new file mode 100644 index 00000000..14e1e448 --- /dev/null +++ b/pkg/display/graph/flamegraph/render.go @@ -0,0 +1,66 @@ +// 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 flamegraph + +import ( + _ "embed" +) + +type StackType int + +var ( + StackTypeUserSpace StackType + StackTypeKernelSpace StackType = 2 +) + +type ProfilingDataTree struct { + Elements []ProfilingDataStackElement +} + +type ProfilingDataStackElement interface { + ID() string + ParentID() string + DumpCount() int64 + Symbol() string + StackType() StackType +} + +type StackElement struct { + ID string + ParentID string + Depth int64 + Left int64 + Count int64 + Type StackType + Symbol string +} + +func GenerateStackElementByProfilingData(element ProfilingDataStackElement, depth, left int64) *StackElement { + return &StackElement{ + ID: element.ID(), + ParentID: element.ParentID(), + Depth: depth, + Left: left, + Count: element.DumpCount(), + Type: element.StackType(), + Symbol: element.Symbol(), + } +} + +//go:embed flamegraph.html +var flameGraphHTML string diff --git a/pkg/display/graph/graph.go b/pkg/display/graph/graph.go index 4f9db130..d33ab528 100644 --- a/pkg/display/graph/graph.go +++ b/pkg/display/graph/graph.go @@ -21,6 +21,8 @@ import ( "fmt" "reflect" + "github.com/apache/skywalking-cli/pkg/display/graph/flamegraph" + api "skywalking.apache.org/repo/goapi/query" "github.com/urfave/cli/v2" @@ -35,23 +37,27 @@ import ( ) type ( - Thermodynamic = api.HeatMap - LinearMetrics = map[string]float64 - MultiLinearMetrics = map[string]LinearMetrics - Trace = api.Trace - TraceBrief = api.TraceBrief - GlobalMetrics = [][]*api.SelectedRecord - GlobalData = dashboard.GlobalData + Thermodynamic = api.HeatMap + LinearMetrics = map[string]float64 + MultiLinearMetrics = map[string]LinearMetrics + Trace = api.Trace + TraceBrief = api.TraceBrief + GlobalMetrics = [][]*api.SelectedRecord + GlobalData = dashboard.GlobalData + EBPFProfilingAnalysis = api.EBPFProfilingAnalyzation + TraceProfilingAnalysis = api.ProfileAnalyzation ) var ( - ThermodynamicType = reflect.TypeOf(Thermodynamic{}) - LinearMetricsType = reflect.TypeOf(LinearMetrics{}) - MultiLinearMetricsType = reflect.TypeOf(MultiLinearMetrics{}) - TraceType = reflect.TypeOf(Trace{}) - TraceBriefType = reflect.TypeOf(TraceBrief{}) - GlobalMetricsType = reflect.TypeOf(GlobalMetrics{}) - GlobalDataType = reflect.TypeOf(&GlobalData{}) + ThermodynamicType = reflect.TypeOf(Thermodynamic{}) + LinearMetricsType = reflect.TypeOf(LinearMetrics{}) + MultiLinearMetricsType = reflect.TypeOf(MultiLinearMetrics{}) + TraceType = reflect.TypeOf(Trace{}) + TraceBriefType = reflect.TypeOf(TraceBrief{}) + GlobalMetricsType = reflect.TypeOf(GlobalMetrics{}) + GlobalDataType = reflect.TypeOf(&GlobalData{}) + EBPFProfilingAnalysisDataType = reflect.TypeOf(&EBPFProfilingAnalysis{}) + TraceProfilingAnalysisDataType = reflect.TypeOf(TraceProfilingAnalysis{}) ) func Display(ctx *cli.Context, displayable *d.Displayable) error { @@ -80,6 +86,12 @@ func Display(ctx *cli.Context, displayable *d.Displayable) error { case GlobalDataType: return db.Display(ctx, data.(*GlobalData)) + case EBPFProfilingAnalysisDataType: + return flamegraph.DisplayByEBPF(ctx, data.(*EBPFProfilingAnalysis)) + + case TraceProfilingAnalysisDataType: + return flamegraph.DisplayByTrace(ctx, data.(api.ProfileAnalyzation)) + default: return fmt.Errorf("type of %T is not supported to be displayed as ascii graph", reflect.TypeOf(data)) } diff --git a/pkg/graphql/profiling/ebpf.go b/pkg/graphql/profiling/ebpf.go new file mode 100644 index 00000000..c38fe07e --- /dev/null +++ b/pkg/graphql/profiling/ebpf.go @@ -0,0 +1,79 @@ +// 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/apache/skywalking-cli/assets" + "github.com/apache/skywalking-cli/pkg/graphql/client" + + "github.com/machinebox/graphql" + + "github.com/urfave/cli/v2" + + api "skywalking.apache.org/repo/goapi/query" +) + +func CreateEBPFProfilingFixedTimeTask(ctx *cli.Context, + condition *api.EBPFProfilingTaskFixedTimeCreationRequest) (api.EBPFProfilingTaskCreationResult, error) { + var response map[string]api.EBPFProfilingTaskCreationResult + + request := graphql.NewRequest(assets.Read("graphqls/profiling/ebpf/CreateEBPFProfilingFixedTimeTask.graphql")) + request.Var("request", condition) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func QueryEBPFProfilingTaskList(ctx *cli.Context, + condition *api.EBPFProfilingTaskCondition) ([]*api.EBPFProfilingTask, error) { + var response map[string][]*api.EBPFProfilingTask + + request := graphql.NewRequest(assets.Read("graphqls/profiling/ebpf/QueryEBPFProfilingTaskList.graphql")) + request.Var("query", condition) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func QueryEBPFProfilingScheduleList(ctx *cli.Context, taskID string, + duration *api.Duration) ([]*api.EBPFProfilingSchedule, error) { + var response map[string][]*api.EBPFProfilingSchedule + + request := graphql.NewRequest(assets.Read("graphqls/profiling/ebpf/QueryEBPFProfilingScheduleList.graphql")) + request.Var("taskID", taskID) + request.Var("duration", duration) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} + +func QueryEBPFProfilingAnalyzation(ctx *cli.Context, taskID string, + timeRanges []*api.EBPFProfilingAnalyzeTimeRange) (*api.EBPFProfilingAnalyzation, error) { + var response map[string]*api.EBPFProfilingAnalyzation + + request := graphql.NewRequest(assets.Read("graphqls/profiling/ebpf/QueryEBPFProflingAnalyzation.graphql")) + request.Var("taskID", taskID) + request.Var("timeRanges", timeRanges) + + err := client.ExecuteQuery(ctx, request, &response) + + return response["result"], err +} diff --git a/pkg/graphql/profile/profile.go b/pkg/graphql/profiling/trace.go similarity index 62% rename from pkg/graphql/profile/profile.go rename to pkg/graphql/profiling/trace.go index 76ee2987..6e78ce58 100644 --- a/pkg/graphql/profile/profile.go +++ b/pkg/graphql/profiling/trace.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package profile +package profiling import ( "github.com/apache/skywalking-cli/assets" @@ -28,10 +28,10 @@ import ( api "skywalking.apache.org/repo/goapi/query" ) -func CreateTask(ctx *cli.Context, condition *api.ProfileTaskCreationRequest) (api.ProfileTaskCreationResult, error) { +func CreateTraceTask(ctx *cli.Context, condition *api.ProfileTaskCreationRequest) (api.ProfileTaskCreationResult, error) { var response map[string]api.ProfileTaskCreationResult - request := graphql.NewRequest(assets.Read("graphqls/profile/CreateTask.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/CreateTask.graphql")) request.Var("condition", condition) err := client.ExecuteQuery(ctx, request, &response) @@ -39,10 +39,10 @@ func CreateTask(ctx *cli.Context, condition *api.ProfileTaskCreationRequest) (ap return response["result"], err } -func GetTaskList(ctx *cli.Context, serviceID, endpointName string) ([]*api.ProfileTask, error) { +func GetTraceProfilingTaskList(ctx *cli.Context, serviceID, endpointName string) ([]*api.ProfileTask, error) { var response map[string][]*api.ProfileTask - request := graphql.NewRequest(assets.Read("graphqls/profile/GetTaskList.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/GetTaskList.graphql")) request.Var("serviceId", serviceID) request.Var("endpointName", endpointName) @@ -51,10 +51,10 @@ func GetTaskList(ctx *cli.Context, serviceID, endpointName string) ([]*api.Profi return response["result"], err } -func GetTaskLogList(ctx *cli.Context, taskID string) ([]*api.ProfileTaskLog, error) { +func GetTraceProfilingTaskLogList(ctx *cli.Context, taskID string) ([]*api.ProfileTaskLog, error) { var response map[string][]*api.ProfileTaskLog - request := graphql.NewRequest(assets.Read("graphqls/profile/GetProfileTaskLogs.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/GetProfileTaskLogs.graphql")) request.Var("taskID", taskID) err := client.ExecuteQuery(ctx, request, &response) @@ -62,10 +62,10 @@ func GetTaskLogList(ctx *cli.Context, taskID string) ([]*api.ProfileTaskLog, err return response["result"], err } -func GetTaskSegmentList(ctx *cli.Context, taskID string) ([]*api.BasicTrace, error) { +func GetTraceProfilingTaskSegmentList(ctx *cli.Context, taskID string) ([]*api.BasicTrace, error) { var response map[string][]*api.BasicTrace - request := graphql.NewRequest(assets.Read("graphqls/profile/GetTaskSegmentList.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/GetTaskSegmentList.graphql")) request.Var("taskId", taskID) err := client.ExecuteQuery(ctx, request, &response) @@ -73,10 +73,10 @@ func GetTaskSegmentList(ctx *cli.Context, taskID string) ([]*api.BasicTrace, err return response["result"], err } -func GetProfiledSegment(ctx *cli.Context, segmentID string) (api.ProfiledSegment, error) { +func GetTraceProfilingSegment(ctx *cli.Context, segmentID string) (api.ProfiledSegment, error) { var response map[string]api.ProfiledSegment - request := graphql.NewRequest(assets.Read("graphqls/profile/GetProfiledSegment.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/GetProfiledSegment.graphql")) request.Var("segmentId", segmentID) err := client.ExecuteQuery(ctx, request, &response) @@ -84,10 +84,10 @@ func GetProfiledSegment(ctx *cli.Context, segmentID string) (api.ProfiledSegment return response["result"], err } -func GetProfileAnalyze(ctx *cli.Context, segmentID string, timeRanges []*api.ProfileAnalyzeTimeRange) (api.ProfileAnalyzation, error) { +func GetTraceProfilingAnalyze(ctx *cli.Context, segmentID string, timeRanges []*api.ProfileAnalyzeTimeRange) (api.ProfileAnalyzation, error) { var response map[string]api.ProfileAnalyzation - request := graphql.NewRequest(assets.Read("graphqls/profile/GetProfileAnalyze.graphql")) + request := graphql.NewRequest(assets.Read("graphqls/profiling/trace/GetProfileAnalyze.graphql")) request.Var("segmentId", segmentID) request.Var("timeRanges", timeRanges)