| // 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 cmd |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "path/filepath" |
| "time" |
| |
| "github.com/apache/skywalking-rover/pkg/tools/operator" |
| |
| "github.com/cilium/ebpf/rlimit" |
| |
| "github.com/apache/skywalking-rover/pkg/module" |
| "github.com/apache/skywalking-rover/pkg/process" |
| "github.com/apache/skywalking-rover/pkg/process/api" |
| "github.com/apache/skywalking-rover/pkg/profiling/task" |
| "github.com/apache/skywalking-rover/pkg/profiling/task/base" |
| |
| "github.com/spf13/cobra" |
| |
| "github.com/apache/skywalking-rover/pkg/boot" |
| ) |
| |
| func newCheckCmd() *cobra.Command { |
| configPath := "" |
| outputPath := "" |
| outputFormat := "" |
| cmd := &cobra.Command{ |
| Use: "check", |
| RunE: func(cmd *cobra.Command, args []string) error { |
| return check(configPath, outputPath, outputFormat) |
| }, |
| } |
| |
| cmd.Flags().StringVarP(&configPath, "config", "c", "configs/rover_configs.yaml", "the rover config file path") |
| cmd.Flags().StringVarP(&outputPath, "output", "o", "output.txt", "the rover check output file") |
| cmd.Flags().StringVarP(&outputFormat, "format", "f", "plain", "the check output format, support \"json\", \"plain\"") |
| return cmd |
| } |
| |
| func check(configPath, outputPath, format string) error { |
| if configPath == "" || outputPath == "" { |
| return fmt.Errorf("the config and output path is required") |
| } |
| |
| err := os.MkdirAll(filepath.Dir(outputPath), os.ModePerm) |
| if err != nil { |
| log.Fatalf("failed to create the output file directory: %v", err) |
| } |
| |
| outFile, err := os.Create(outputPath) |
| if err != nil { |
| log.Fatalf("failed to create the output file: %v", err) |
| } |
| defer outFile.Close() |
| |
| notify := make(chan bool, 1) |
| go func(notify chan bool) { |
| err = boot.RunModules(context.Background(), configPath, func(manager *module.Manager) { |
| // startup success |
| processModuleStartSuccess(notify, nil, manager, outFile, format) |
| }) |
| if err != nil { |
| // startup failure |
| processModuleStartSuccess(notify, err, nil, outFile, format) |
| } |
| }(notify) |
| |
| <-notify |
| return nil |
| } |
| |
| func processModuleStartSuccess(notify chan bool, err error, mgr *module.Manager, file io.Writer, format string) { |
| data := &outputData{} |
| defer func() { |
| writeOutput(data, file, format) |
| notify <- true |
| }() |
| if err != nil { |
| data.Startup = err |
| return |
| } |
| uname, err := operator.GetOSUname() |
| if err != nil { |
| data.Startup = err |
| return |
| } |
| data.Kernel = uname.Release |
| |
| if err := rlimit.RemoveMemlock(); err != nil { |
| data.Startup = err |
| return |
| } |
| |
| // wait processes |
| processModule := mgr.FindModule(process.ModuleName).(*process.Module) |
| var processes []api.ProcessInterface |
| for i := 0; i < 3; i++ { |
| processes = processModule.GetAllProcesses() |
| if len(processes) != 0 { |
| break |
| } |
| time.Sleep(time.Second) |
| } |
| if len(processes) == 0 { |
| data.Startup = fmt.Errorf("no process") |
| return |
| } |
| |
| ctx := context.Background() |
| data.OnCPU = testOnCPUProfiling(ctx, mgr, processes[0]) |
| data.OffCPU = testOffCPUProfiling(ctx, mgr, processes[0]) |
| data.Network = testNetworkProfiling(ctx, mgr, processes[0]) |
| } |
| |
| func testOnCPUProfiling(ctx context.Context, mgr *module.Manager, p api.ProcessInterface) error { |
| return testWithRunner(ctx, base.TargetTypeOnCPU, &base.TaskConfig{ |
| OnCPU: &base.OnCPUConfig{ |
| Period: "9ms", |
| }, |
| }, mgr, p) |
| } |
| |
| func testOffCPUProfiling(ctx context.Context, mgr *module.Manager, p api.ProcessInterface) error { |
| return testWithRunner(ctx, base.TargetTypeOffCPU, &base.TaskConfig{}, mgr, p) |
| } |
| |
| func testNetworkProfiling(ctx context.Context, mgr *module.Manager, p api.ProcessInterface) error { |
| return testWithRunner(ctx, base.TargetTypeNetworkTopology, &base.TaskConfig{ |
| Network: &base.NetworkConfig{ |
| MeterPrefix: "test", |
| ReportInterval: "10ms", |
| ProtocolAnalyze: base.ProtocolAnalyzeConfig{ |
| PerCPUBufferSize: "10K", |
| Parallels: 1, |
| QueueSize: 5000, |
| }, |
| }, |
| }, mgr, p) |
| } |
| |
| func testWithRunner(ctx context.Context, taskType base.TargetType, taskConfig *base.TaskConfig, |
| moduleManager *module.Manager, p api.ProcessInterface) error { |
| runner, err := task.NewProfilingRunner(taskType, taskConfig, moduleManager) |
| if err != nil { |
| return err |
| } |
| |
| if err := runner.Init(&base.ProfilingTask{}, []api.ProcessInterface{p}); err != nil { |
| return err |
| } |
| |
| if err := runner.Run(ctx, func() { |
| _ = runner.Stop() |
| }); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func writeOutput(data *outputData, file io.Writer, format string) { |
| if format != "json" { |
| sprintData := fmt.Sprintf("Kernel: %s\nStartup: %s\nOnCPU: %s\nOffCPU: %s\nNetwork: %s", |
| data.Kernel, errorOrSuccess(data.Startup), errorOrSuccess(data.OnCPU), |
| errorOrSuccess(data.OffCPU), errorOrSuccess(data.Network)) |
| _, _ = file.Write([]byte(sprintData)) |
| return |
| } |
| // some error could not be marshaled, such as multierror |
| jsonData := &outputDataJSON{ |
| Kernel: data.Kernel, |
| Startup: errorOrSuccess(data.Startup), |
| OnCPU: errorOrSuccess(data.OnCPU), |
| OffCPU: errorOrSuccess(data.OffCPU), |
| Network: errorOrSuccess(data.Network), |
| } |
| marshal, err := json.Marshal(jsonData) |
| if err != nil { |
| log.Printf("format the output failure: %v", err) |
| return |
| } |
| _, _ = file.Write(marshal) |
| } |
| |
| func errorOrSuccess(data error) string { |
| if data != nil { |
| return data.Error() |
| } |
| return "true" |
| } |
| |
| type outputData struct { |
| Kernel string |
| Startup error |
| OnCPU error |
| OffCPU error |
| Network error |
| } |
| |
| type outputDataJSON struct { |
| Kernel string |
| Startup string |
| OnCPU string |
| OffCPU string |
| Network string |
| } |