-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Wei Fu <weifu@microsoft.com>
- Loading branch information
Showing
3 changed files
with
275 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,128 @@ | ||
package runner | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"sort" | ||
|
||
"github.com/Azure/kperf/api/types" | ||
"github.com/Azure/kperf/request" | ||
|
||
"github.com/urfave/cli" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
// Command represents runner sub-command. | ||
// | ||
// Subcommand runner is to create request load to apiserver. | ||
// | ||
// NOTE: It can work with subcommand multirunners. The multirunners subcommand | ||
// will deploy subcommand runner in pod. Details in ../multirunners. | ||
// | ||
// Command line interface: | ||
// | ||
// kperf runner --help | ||
// | ||
// Options: | ||
// | ||
// --kubeconfig PATH (default: empty_string, use token if it's empty) | ||
// --load-config PATH (default: empty_string, required, the config defined in api/types/load_traffic.go) | ||
// --conns INT (default: 1, Total number of connections. It can override corresponding value defined by --load-config) | ||
// --rate INT (default: 0, Maximum requests per second. It can override corresponding value defined by --load-config) | ||
// --total INT (default: 1000, Total number of request. It can override corresponding value defined by --load-config) | ||
// Command represents runner subcommand. | ||
var Command = cli.Command{ | ||
Name: "runner", | ||
Usage: "run a load test to kube-apiserver", | ||
Flags: []cli.Flag{}, | ||
Usage: "Setup benchmark to kube-apiserver from one endpoint", | ||
Subcommands: []cli.Command{ | ||
runCommand, | ||
}, | ||
} | ||
|
||
var runCommand = cli.Command{ | ||
Name: "run", | ||
Usage: "run a benchmark test to kube-apiserver", | ||
Flags: []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "kubeconfig", | ||
Usage: "Path to the kubeconfig file", | ||
}, | ||
cli.StringFlag{ | ||
Name: "config", | ||
Usage: "Path to the configuration file", | ||
Required: true, | ||
}, | ||
cli.IntFlag{ | ||
Name: "conns", | ||
Usage: "Total number of connections. It can override corresponding value defined by --config", | ||
Value: 1, | ||
}, | ||
cli.IntFlag{ | ||
Name: "rate", | ||
Usage: "Maximum requests per second (Zero means no limitation). It can override corresponding value defined by --config", | ||
}, | ||
cli.IntFlag{ | ||
Name: "total", | ||
Usage: "Total number of requests. It can override corresponding value defined by --config", | ||
Value: 1000, | ||
}, | ||
cli.StringFlag{ | ||
Name: "user-agent", | ||
Usage: "User Agent", | ||
}, | ||
}, | ||
Action: func(cliCtx *cli.Context) error { | ||
// 1. Parse options | ||
// 2. Setup producer-consumer goroutines | ||
// 2.1 Use go limter to generate request | ||
// 2.2 Use client-go's client to file requests | ||
// 3. Build progress tracker to track failure number and P99/P95/P90 latencies. | ||
// 4. Export summary in stdout. | ||
return fmt.Errorf("runner - not implemented") | ||
profileCfg, err := loadConfig(cliCtx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
kubeCfgPath := cliCtx.String("kubeconfig") | ||
userAgent := cliCtx.String("user-agent") | ||
|
||
conns := profileCfg.Spec.Conns | ||
rate := profileCfg.Spec.Rate | ||
restClis, err := request.NewClients(kubeCfgPath, conns, userAgent, rate) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
stats, err := request.Schedule(context.TODO(), &profileCfg.Spec, restClis) | ||
if err != nil { | ||
return err | ||
} | ||
printResponseStats(stats) | ||
return nil | ||
}, | ||
} | ||
|
||
// loadConfig loads and validates the config. | ||
func loadConfig(cliCtx *cli.Context) (*types.LoadProfile, error) { | ||
var profileCfg types.LoadProfile | ||
|
||
cfgPath := cliCtx.String("config") | ||
|
||
cfgInRaw, err := os.ReadFile(cfgPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read file %s: %w", cfgPath, err) | ||
} | ||
|
||
if err := yaml.Unmarshal(cfgInRaw, &profileCfg); err != nil { | ||
return nil, fmt.Errorf("failed to unmarshal %s from yaml format: %w", cfgPath, err) | ||
} | ||
|
||
// override value by flags | ||
// | ||
// TODO(weifu): do not override if flag is not set | ||
profileCfg.Spec.Rate = cliCtx.Int("rate") | ||
profileCfg.Spec.Conns = cliCtx.Int("conns") | ||
profileCfg.Spec.Total = cliCtx.Int("total") | ||
|
||
if err := profileCfg.Validate(); err != nil { | ||
return nil, err | ||
} | ||
return &profileCfg, nil | ||
} | ||
|
||
// printResponseStats prints ResponseStats into stdout. | ||
func printResponseStats(stats *types.ResponseStats) { | ||
fmt.Println("Response stat:") | ||
fmt.Printf(" Total: %v\n", stats.Total) | ||
fmt.Printf(" Failures: %v\n", stats.Failures) | ||
fmt.Printf(" Duration: %v\n", stats.Duration) | ||
fmt.Printf(" Requests/sec: %.2f\n", float64(stats.Total)/stats.Duration.Seconds()) | ||
|
||
fmt.Println(" Latency Distribution:") | ||
keys := make([]float64, 0, len(stats.Latencies)) | ||
for q := range stats.Latencies { | ||
keys = append(keys, q) | ||
} | ||
sort.Float64s(keys) | ||
|
||
for _, q := range keys { | ||
fmt.Printf(" [%.2f] %.3fs\n", q, stats.Latencies[q]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.