From 43fcb00990de5ae553d8e0ed95f084feaf8f9495 Mon Sep 17 00:00:00 2001 From: rogerwelin Date: Thu, 11 Jun 2020 00:59:25 +0200 Subject: [PATCH 1/3] bumped urfave/cli to v2, refactored cli code, merge run & run-file into run --- .gitignore | 4 +- cmd/cassowary/cli.go | 221 +++++++++++++------------------------------ go.mod | 2 +- go.sum | 3 + 4 files changed, 70 insertions(+), 160 deletions(-) diff --git a/.gitignore b/.gitignore index 22d10d5..f9ab8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ apa* out.json go-dist -.theia/ -/.theia -.theia/settings.json \ No newline at end of file +urls.txt diff --git a/cmd/cassowary/cli.go b/cmd/cassowary/cli.go index 9c44c29..7edc92b 100644 --- a/cmd/cassowary/cli.go +++ b/cmd/cassowary/cli.go @@ -14,7 +14,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/fatih/color" "github.com/rogerwelin/cassowary/pkg/client" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) var ( @@ -94,12 +94,13 @@ func runLoadTest(c *client.Cassowary) error { } func validateCLI(c *cli.Context) error { - prometheusEnabled := false var header []string var httpMethod string var data []byte duration := 0 + var urlSuffixes []string + fileMode := false if c.Int("concurrency") == 0 { return errConcurrencyLevel @@ -136,6 +137,15 @@ func validateCLI(c *cli.Context) error { } } + if c.String("file") != "" { + var err error + urlSuffixes, err = readLocalRemoteFile(c.String("file")) + if err != nil { + return nil + } + fileMode = true + } + if c.String("postfile") != "" { httpMethod = "POST" fileData, err := readFile(c.String("postfile")) @@ -176,7 +186,7 @@ func validateCLI(c *cli.Context) error { } cass := &client.Cassowary{ - FileMode: false, + FileMode: fileMode, BaseURL: c.String("url"), ConcurrencyLevel: c.Int("concurrency"), Requests: c.Int("requests"), @@ -191,56 +201,8 @@ func validateCLI(c *cli.Context) error { DisableKeepAlive: c.Bool("disable-keep-alive"), Timeout: c.Int("timeout"), HTTPMethod: httpMethod, - Data: data, - } - - return runLoadTest(cass) -} - -func validateCLIFile(c *cli.Context) error { - prometheusEnabled := false - var header []string - - if c.Int("concurrency") == 0 { - return errConcurrencyLevel - } - - if client.IsValidURL(c.String("url")) == false { - return errNotValidURL - } - - if c.String("prompushgwurl") != "" { - prometheusEnabled = true - } - - if c.String("header") != "" { - length := 0 - length, header = client.SplitHeader(c.String("header")) - if length != 2 { - return errNotValidHeader - } - } - - urlSuffixes, err := readLocalRemoteFile(c.String("file")) - if err != nil { - return nil - } - - cass := &client.Cassowary{ - FileMode: true, - BaseURL: c.String("url"), - ConcurrencyLevel: c.Int("concurrency"), - RequestHeader: header, - PromExport: prometheusEnabled, - PromURL: c.String("prompushgwurl"), - Cloudwatch: c.Bool("cloudwatch"), - ExportMetrics: c.Bool("json-metrics"), - ExportMetricsFile: c.String("json-metrics-file"), - DisableKeepAlive: c.Bool("diable-keep-alive"), - Timeout: c.Int("timeout"), - Requests: c.Int("requests"), URLPaths: urlSuffixes, - HTTPMethod: "GET", + Data: data, } return runLoadTest(cass) @@ -254,143 +216,90 @@ func runCLI(args []string) { app.EnableBashCompletion = true app.Usage = "" app.Version = version - app.Commands = []cli.Command{ - { - Name: "run-file", - Usage: "start load test in spread mode", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "u, url", - Usage: "the base url (absoluteURI) to be used", - Required: true, - }, - cli.IntFlag{ - Name: "c, concurrency", - Usage: "number of concurrent users", - Required: true, - }, - cli.IntFlag{ - Name: "n, requests", - Usage: "number of requests to perform", - }, - cli.IntFlag{ - Name: "t, timeout", - Usage: "http client timeout", - Value: 5, - }, - cli.StringFlag{ - Name: "f, file", - Usage: "specify `FILE` path, local or www, containing the url suffixes", - Required: true, - }, - cli.StringFlag{ - Name: "p, prompushgwurl", - Usage: "specify prometheus push gateway url to send metrics (optional)", - }, - cli.BoolFlag{ - Name: "C, cloudwatch", - Usage: "enable to send metrics to AWS Cloudwatch", - }, - cli.StringFlag{ - Name: "H, header", - Usage: "add arbitrary header, eg. 'Host: www.example.com'", - }, - cli.BoolFlag{ - Name: "F, json-metrics", - Usage: "outputs metrics to a json file by setting flag to true", - }, - cli.StringFlag{ - Name: "json-metrics-file", - Usage: "outputs metrics to a custom json filepath, if json-metrics is set to true", - }, - cli.BoolFlag{ - Name: "disable-keep-alive", - Usage: "use this flag to disable http keep-alive", - }, - cli.StringFlag{ - Name: "ca", - Usage: "ca certificate to verify peer against", - }, - cli.StringFlag{ - Name: "cert", - Usage: "client authentication certificate", - }, - cli.StringFlag{ - Name: "key", - Usage: "client authentication key", - }, - }, - Action: validateCLIFile, - }, + app.Commands = []*cli.Command{ { Name: "run", Usage: "start load-test", Flags: []cli.Flag{ - cli.StringFlag{ - Name: "u, url", + &cli.StringFlag{ + Name: "u", + Aliases: []string{"url"}, Usage: "the url (absoluteURI) to be used", Required: true, }, - cli.IntFlag{ - Name: "c, concurrency", - Usage: "number of concurrent users", - Required: true, + &cli.IntFlag{ + Name: "c", + Aliases: []string{"concurrency"}, + Usage: "number of concurrent users", + Value: 1, }, - cli.IntFlag{ - Name: "n, requests", - Usage: "number of requests to perform", - Required: true, + &cli.IntFlag{ + Name: "n", + Aliases: []string{"requests"}, + Usage: "number of requests to perform", + Value: 1, + }, + &cli.StringFlag{ + Name: "f", + Aliases: []string{"file"}, + Usage: "file-slurp mode: specify `FILE` path, local or www, containing the url suffixes", }, - cli.StringFlag{ - Name: "d, duration", - Usage: "set the duration in seconds of the load test (example: do 100 requests in a duration of 30s)", + &cli.StringFlag{ + Name: "d", + Aliases: []string{"duration"}, + Usage: "set the duration in seconds of the load test (example: do 100 requests in a duration of 30s)", }, - cli.IntFlag{ - Name: "t, timeout", - Usage: "http client timeout", - Value: 5, + &cli.IntFlag{ + Name: "t", + Aliases: []string{"timeout"}, + Usage: "http client timeout", + Value: 5, }, - cli.StringFlag{ - Name: "p, prompushgwurl", - Usage: "specify prometheus push gateway url to send metrics (optional)", + &cli.StringFlag{ + Name: "p", + Aliases: []string{"prompushgwurl"}, + Usage: "specify prometheus push gateway url to send metrics (optional)", }, - cli.BoolFlag{ - Name: "C, cloudwatch", - Usage: "enable to send metrics to AWS Cloudwatch", + &cli.BoolFlag{ + Name: "C", + Aliases: []string{"cloudwatch"}, + Usage: "enable to send metrics to AWS Cloudwatch", }, - cli.StringFlag{ - Name: "H, header", - Usage: "add arbitrary header, eg. 'Host: www.example.com'", + &cli.StringFlag{ + Name: "H", + Aliases: []string{"header"}, + Usage: "add arbitrary header, eg. 'Host: www.example.com'", }, - cli.BoolFlag{ - Name: "F, json-metrics", - Usage: "outputs metrics to a json file by setting flag to true", + &cli.BoolFlag{ + Name: "F", + Aliases: []string{"json-metrics"}, + Usage: "outputs metrics to a json file by setting flag to true", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "postfile", Usage: "file containing data to POST (content type will default to application/json)", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "putfile", Usage: "file containig data to PUT (content type will default to application/json)", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "json-metrics-file", Usage: "outputs metrics to a custom json filepath, if json-metrics is set to true", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "disable-keep-alive", Usage: "use this flag to disable http keep-alive", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "ca", Usage: "ca certificate to verify peer against", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "cert", Usage: "client authentication certificate", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "key", Usage: "client authentication key", }, diff --git a/go.mod b/go.mod index deac9d2..01f6578 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( github.com/mattn/go-isatty v0.0.10 // indirect github.com/prometheus/client_golang v1.3.0 github.com/schollz/progressbar v1.0.0 - github.com/urfave/cli v1.22.1 + github.com/urfave/cli/v2 v2.2.0 ) diff --git a/go.sum b/go.sum index 2820993..4c853c1 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,9 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 68522b3e434515d4ef4fcf94baca72891c962043 Mon Sep 17 00:00:00 2001 From: rogerwelin Date: Thu, 11 Jun 2020 01:04:46 +0200 Subject: [PATCH 2/3] update readme to reflect refactoring --- README-ZH.md | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README-ZH.md b/README-ZH.md index e187fa9..acce16a 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -71,7 +71,7 @@ Summary: 示例:访问外部文件指定的URL路径(外部文件也可以是http路径的) ```bash -$ ./cassowary run-file -u http://localhost:8000 -c 10 -f urlpath.txt +$ ./cassowary run -u http://localhost:8000 -c 10 -f urlpath.txt Starting Load Test with 3925 requests using 10 concurrent users diff --git a/README.md b/README.md index 4b1b7d6..979f472 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Installation Grab a pre-built binary from the [GitHub Releases page](https://github.com/rogerwelin/cassowary/releases). You can optionally put the **cassowary** binary in your `PATH` so you can run cassowary from any location. Alternative you can: -### Homebrew on MAC OSX +### Homebrew on Mac OSX You can install **cassowary** using the Homebrew package manager on Mac: ```bash @@ -107,14 +107,14 @@ Summary: Example running **cassowary** in file slurp mode where all URL paths are specified from an external file (which can also be fetched from http if specified). By default cassowary will, without the -n flag specified, make one request per path specified in the file. However with the -n flag you can also specify how many request you want cassowary to generate against those URL paths. Example: ```bash -$ ./cassowary run-file -u http://localhost:8000 -c 1 -f urlpath.txt +$ ./cassowary run -u http://localhost:8000 -c 1 -f urlpath.txt Starting Load Test with 5 requests using 1 concurrent users [ omitted ] -$ ./cassowary run-file -u http://localhost:8000 -c 10 -n 100 -f urlpath.txt +$ ./cassowary run -u http://localhost:8000 -c 10 -n 100 -f urlpath.txt Starting Load Test with 100 requests using 10 concurrent users From 1c2ef1f60a5ae5ae536b37f3c5688150d43fafba Mon Sep 17 00:00:00 2001 From: rogerwelin Date: Thu, 11 Jun 2020 09:01:56 +0200 Subject: [PATCH 3/3] fix bug when duration and slurpmode was specified --- pkg/client/load.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/client/load.go b/pkg/client/load.go index 649cd64..c8e7d56 100644 --- a/pkg/client/load.go +++ b/pkg/client/load.go @@ -188,6 +188,7 @@ func (c *Cassowary) Coordinate() (ResultMetrics, error) { nextTick := durationMS / c.Requests ticker := time.NewTicker(time.Duration(nextTick) * time.Millisecond) done := make(chan bool) + iter := 0 go func() { for { @@ -195,7 +196,12 @@ func (c *Cassowary) Coordinate() (ResultMetrics, error) { case <-done: return case _ = <-ticker.C: - workerChan <- "a" + if c.FileMode { + workerChan <- c.URLPaths[iter] + iter++ + } else { + workerChan <- "a" + } } } }()