cmd/apmsoak/run.go (130 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package main import ( "context" "errors" "fmt" "os" "sort" "strings" "time" "github.com/spf13/cobra" "go.elastic.co/ecszap" "go.uber.org/zap" "github.com/elastic/apm-perf/internal/soaktest" ) type RunOptions struct { Scenario string ScenariosPath string ServerURL string SecretToken string APIKeys string Headers map[string]string BypassProxy bool Loglevel string IgnoreErrors bool RunForever bool RunDuration time.Duration } func (opts *RunOptions) toRunnerConfig() (*soaktest.RunnerConfig, error) { apiKeys := make(map[string]string) if opts.APIKeys != "" { pairs := strings.Split(opts.APIKeys, ",") for _, pair := range pairs { kv := strings.Split(pair, ":") if len(kv) != 2 { return nil, errors.New("invalid api keys provided. example: project_id:my_api_key") } apiKeys[kv[0]] = kv[1] } } return &soaktest.RunnerConfig{ Scenario: opts.Scenario, ScenariosPath: opts.ScenariosPath, ServerURL: opts.ServerURL, SecretToken: opts.SecretToken, APIKeys: apiKeys, Headers: opts.Headers, BypassProxy: opts.BypassProxy, IgnoreErrors: opts.IgnoreErrors, RunForever: opts.RunForever, RunDuration: opts.RunDuration, }, nil } type headersFlag map[string]string func (f headersFlag) String() string { keys := make([]string, 0, len(f)) for k := range f { keys = append(keys, k) } sort.Strings(keys) for i, k := range keys { keys[i] = fmt.Sprintf("%s=%s", k, f[k]) } return strings.Join(keys, ",") } func (f headersFlag) Set(s string) error { k, v, ok := strings.Cut(s, "=") if !ok { return fmt.Errorf("expected k=v, got %q", s) } f[k] = v return nil } func (f headersFlag) Type() string { return "k=v" } func NewCmdRun() *cobra.Command { options := &RunOptions{ Headers: make(map[string]string), } cmd := &cobra.Command{ Use: "run", Short: "Run apmsoak", RunE: func(cmd *cobra.Command, args []string) error { logger := getLogger(options.Loglevel) config, err := options.toRunnerConfig() if err != nil { logger.Fatal("Fail to parse flags", zap.Error(err)) } logger.Debug("parsed configs", zap.Object("config", config)) runner, err := soaktest.NewRunner(config, logger) if err != nil { logger.Fatal("Fail to initialize runner", zap.Error(err)) } if err := runner.Run(cmd.Context()); err != nil { if !errors.Is(err, context.Canceled) { logger.Error("runner exited with error", zap.Error(err)) return err } } return nil }, } cmd.Flags().StringVar(&options.ServerURL, "server-url", "", "Server URL (default http://127.0.0.1:8200), if specified <project_id>, it will be replaced with the project_id provided by the config, (example: https://<project_id>.apm.elastic.cloud)") cmd.Flags().StringVar(&options.Scenario, "scenario", "steady", "Specify which scenario to use. the value should match one of the scenario key defined in given scenarios YAML file") cmd.Flags().StringVarP(&options.ScenariosPath, "file", "f", "./scenarios.yml", "Path to scenarios file") cmd.Flags().StringVar(&options.SecretToken, "secret-token", "", "Secret token for APM Server. Managed intake service doesn't support secret token") cmd.Flags().StringVar(&options.APIKeys, "api-keys", "", "API keys for managed service. Specify key value pairs as `project_id_1:my_api_key,project_id_2:my_key`") cmd.Flags().Var(headersFlag(options.Headers), "header", "Extra headers to send. <project_id> will be replaced in header values.") cmd.Flags().BoolVar(&options.BypassProxy, "bypass-proxy", false, "Detach from proxy dependency and provide projectID via header. Useful when testing locally") cmd.Flags().StringVar(&options.Loglevel, "log-level", "info", "Specify the log level to use when running this command. Supported values: debug, info, warn, error") cmd.Flags().BoolVar(&options.IgnoreErrors, "ignore-errors", false, "Ignore HTTP errors while sending events") cmd.Flags().BoolVar(&options.RunForever, "run-forever", false, "Continue running the soak test until a signal is received to stop it") cmd.Flags().DurationVar(&options.RunDuration, "duration", 0, "duration of the run") return cmd } func getLogger(logLevel string) *zap.Logger { encoderConfig := ecszap.NewDefaultEncoderConfig() level := zap.InfoLevel switch logLevel { case "debug": level = zap.DebugLevel case "warn": level = zap.WarnLevel case "error": level = zap.ErrorLevel } core := ecszap.NewCore(encoderConfig, os.Stdout, level) logger := zap.New(core, zap.AddCaller()) return logger }