diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d318aa15..9b3d5f04 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest env: GO111MODULE: on - GOLANGCI_LINT_VERSION: v1.27.0 + GOLANGCI_LINT_VERSION: v1.31.0 steps: - name: Set up Go diff --git a/.golangci.yml b/.golangci.yml index 56729ef3..f0333c48 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,5 +24,17 @@ issues: - path: cmd/ghz/main.go text: "Error return value of `logger.Sync` is not checked" + # sync.once copy in pacer test + - path: load/pacer_test.go + text: "copylocks" + + # TODO fix protobuf deprecated + - path: runner/ + text: "SA1019: package github.com/golang/protobuf" + + # TODO fix protobuf deprecated + - path: protodesc/ + text: "SA1019: package github.com/golang/protobuf" + - path: runner/requester.go text: "SA1019: grpc.WithBalancerName" \ No newline at end of file diff --git a/Makefile b/Makefile index cd3361bd..1bb1a2e7 100644 --- a/Makefile +++ b/Makefile @@ -36,10 +36,6 @@ else OPEN_COVERAGE_HTML := endif -################################################# -##### Everything below should not be edited ##### -################################################# - # UNAME_OS stores the value of uname -s. UNAME_OS := $(shell uname -s) # UNAME_ARCH stores the value of uname -m. @@ -48,25 +44,16 @@ UNAME_ARCH := $(shell uname -m) # TMP_BASE is the base directory used for TMP. # Use TMP and not TMP_BASE as the temporary directory. TMP_BASE := .tmp -# TMP is the temporary directory used. -# This is based on UNAME_OS and UNAME_ARCH to make sure there are no issues -# switching between platform builds. -TMP := $(TMP_BASE)/$(UNAME_OS)/$(UNAME_ARCH) -# TMP_BIN is where we install binaries for usage during development. -TMP_BIN = $(TMP)/bin # TMP_COVERAGE is where we store code coverage files. TMP_COVERAGE := $(TMP_BASE)/coverage # Run all by default when "make" is invoked. .DEFAULT_GOAL := all -# Install all the build and lint dependencies -setup: - if [ ! -f $(GOPATH)/bin/tparse ]; then go get github.com/mfridman/tparse; fi; - if [ ! -f $(GOPATH)/bin/goimports ]; then go get golang.org/x/tools/cmd/goimports; fi; - if [ ! -f $(GOPATH)/bin/golangci-lint ]; then curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.23.8; fi; - go mod download -.PHONY: setup +# Tools +.PHONY: tools +tools: + go generate -tags tools tools/tools.go # All runs the default lint, test, and code coverage targets. .PHONY: all @@ -88,11 +75,12 @@ lint: test: go test $(GO_TEST_FLAGS) $(GO_PKGS) -# gofmt and goimports all go files +# Formats using gofmt and goimports all go files .PHONY: fmt fmt: find . -name '*.go' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done +# Build .PHONY: build build: CGO_ENABLED=0 go build --ldflags="-s -w" -o $(DIST_DIR)/ghz ./cmd/ghz/... diff --git a/README.md b/README.md index ad368218..e2b4c9bb 100644 --- a/README.md +++ b/README.md @@ -70,16 +70,31 @@ Flags: --key= File containing client private key, to present to the server. Must also provide -cert option. --cname= Server name override when validating TLS certificate - useful for self signed certs. --skipTLS Skip TLS client verification of the server's certificate chain and host name. - --skipFirst=0 Skip the first X requests from the timing calculations (useful for initial warmup) --insecure Use plaintext and insecure connection. --authority= Value to be used as the :authority pseudo-header. Only works if -insecure is used. - -c, --concurrency=50 Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50. + --async Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one. + -r, --rps=0 Requests per second (RPS) rate limit for constant load schedule. Default is no rate limit. + --load-schedule="const" Specifies the load schedule. Options are const, step, or line. Default is const. + --load-start=0 Specifies the RPS load start value for step or line schedules. + --load-step=0 Specifies the load step value or slope value. + --load-end=0 Specifies the load end value for step or line load schedules. + --load-step-duration=0 Specifies the load step duration value for step load schedule. + --load-max-duration=0 Specifies the max load duration value for step or line load schedule. + -c, --concurrency=50 Number of request workers to run concurrently for const concurrency schedule. Default is 50. + --concurrency-schedule="const" + Concurrency change schedule. Options are const, step, or line. Default is const. + --concurrency-start=0 Concurrency start value for step and line concurrency schedules. + --concurrency-end=0 Concurrency end value for step and line concurrency schedules. + --concurrency-step=1 Concurrency step / slope value for step and line concurrency schedules. + --concurrency-step-duration=0 + Specifies the concurrency step duration value for step concurrency schedule. + --concurrency-max-duration=0 + Specifies the max concurrency adjustment duration value for step or line concurrency schedule. -n, --total=200 Number of requests to run. Default is 200. - -q, --qps=0 Rate limit, in queries per second (QPS). Default is no rate limit. -t, --timeout=20s Timeout for each request. Default is 20s, use 0 for infinite. -z, --duration=0 Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, n is ignored. Examples: -z 10s -z 3m. -x, --max-duration=0 Maximum duration of application to send requests with n setting respected. If duration is reached before n requests are completed, application stops and exits. Examples: -x 10s -x 3m. - --duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. + --duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. Default is close. -d, --data= The call data as stringified JSON. If the value is '@' then the request contents are read from stdin. -D, --data-file= File path for call data JSON file. Examples: /home/user/file.json or ./file.json. -b, --binary The call data comes as serialized binary message or multiple count-prefixed messages read from stdin. @@ -90,6 +105,7 @@ Flags: --reflect-metadata= Reflect metadata as stringified JSON used only for reflection request. -o, --output= Output path. If none provided stdout is used. -O, --format= Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary. + --skipFirst=0 Skip the first X requests when doing the results tally. --connections=1 Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1. --connect-timeout=10s Connection timeout for the initial connection dial. Default is 10s. --keepalive=0 Keepalive time duration. Only used if present and above 0. diff --git a/cmd/ghz/main.go b/cmd/ghz/main.go index b5445a16..5ef03ac2 100644 --- a/cmd/ghz/main.go +++ b/cmd/ghz/main.go @@ -64,10 +64,6 @@ var ( skipVerify = kingpin.Flag("skipTLS", "Skip TLS client verification of the server's certificate chain and host name."). Default("false").IsSetByUser(&isSkipSet).Bool() - isSkipFirstSet = false - skipFirst = kingpin.Flag("skipFirst", "Skip the first X requests when doing the results tally."). - Default("0").IsSetByUser(&isSkipFirstSet).Uint() - isInsecSet = false insecure = kingpin.Flag("insecure", "Use plaintext and insecure connection."). Default("false").IsSetByUser(&isInsecSet).Bool() @@ -77,18 +73,72 @@ var ( PlaceHolder(" ").IsSetByUser(&isAuthSet).String() // Run + isAsyncSet = false + async = kingpin.Flag("async", "Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one."). + Default("false").IsSetByUser(&isAsyncSet).Bool() + + isRPSSet = false + rps = kingpin.Flag("rps", "Requests per second (RPS) rate limit for constant load schedule. Default is no rate limit."). + Default("0").Short('r').IsSetByUser(&isRPSSet).Uint() + + isScheduleSet = false + schedule = kingpin.Flag("load-schedule", "Specifies the load schedule. Options are const, step, or line. Default is const."). + Default("const").IsSetByUser(&isScheduleSet).String() + + isLoadStartSet = false + loadStart = kingpin.Flag("load-start", "Specifies the RPS load start value for step or line schedules."). + Default("0").IsSetByUser(&isLoadStartSet).Uint() + + isLoadStepSet = false + loadStep = kingpin.Flag("load-step", "Specifies the load step value or slope value."). + Default("0").IsSetByUser(&isLoadStepSet).Int() + + isLoadEndSet = false + loadEnd = kingpin.Flag("load-end", "Specifies the load end value for step or line load schedules."). + Default("0").IsSetByUser(&isLoadEndSet).Uint() + + isLoadStepDurSet = false + loadStepDuration = kingpin.Flag("load-step-duration", "Specifies the load step duration value for step load schedule."). + Default("0").IsSetByUser(&isLoadStepDurSet).Duration() + + isLoadMaxDurSet = false + loadMaxDuration = kingpin.Flag("load-max-duration", "Specifies the max load duration value for step or line load schedule."). + Default("0").IsSetByUser(&isLoadMaxDurSet).Duration() + + // Concurrency isCSet = false - c = kingpin.Flag("concurrency", "Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50."). + c = kingpin.Flag("concurrency", "Number of request workers to run concurrently for const concurrency schedule. Default is 50."). Short('c').Default("50").IsSetByUser(&isCSet).Uint() + isCScheduleSet = false + cschdule = kingpin.Flag("concurrency-schedule", "Concurrency change schedule. Options are const, step, or line. Default is const."). + Default("const").IsSetByUser(&isCScheduleSet).String() + + isCStartSet = false + cStart = kingpin.Flag("concurrency-start", "Concurrency start value for step and line concurrency schedules."). + Default("0").IsSetByUser(&isCStartSet).Uint() + + isCEndSet = false + cEnd = kingpin.Flag("concurrency-end", "Concurrency end value for step and line concurrency schedules."). + Default("0").IsSetByUser(&isCEndSet).Uint() + + isCStepSet = false + cstep = kingpin.Flag("concurrency-step", "Concurrency step / slope value for step and line concurrency schedules."). + Default("1").IsSetByUser(&isCStepSet).Int() + + isCStepDurSet = false + cStepDuration = kingpin.Flag("concurrency-step-duration", "Specifies the concurrency step duration value for step concurrency schedule."). + Default("0").IsSetByUser(&isCStepDurSet).Duration() + + isCMaxDurSet = false + cMaxDuration = kingpin.Flag("concurrency-max-duration", "Specifies the max concurrency adjustment duration value for step or line concurrency schedule."). + Default("0").IsSetByUser(&isCMaxDurSet).Duration() + + // Other isNSet = false n = kingpin.Flag("total", "Number of requests to run. Default is 200."). Short('n').Default("200").IsSetByUser(&isNSet).Uint() - isQSet = false - q = kingpin.Flag("qps", "Rate limit, in queries per second (QPS). Default is no rate limit."). - Default("0").Short('q').IsSetByUser(&isQSet).Uint() - isTSet = false t = kingpin.Flag("timeout", "Timeout for each request. Default is 20s, use 0 for infinite."). Default("20s").Short('t').IsSetByUser(&isTSet).Duration() @@ -102,7 +152,7 @@ var ( Short('x').Default("0").IsSetByUser(&isXSet).Duration() isZStopSet = false - zstop = kingpin.Flag("duration-stop", "Specifies how duration stop is reported. Options are close, wait or ignore."). + zstop = kingpin.Flag("duration-stop", "Specifies how duration stop is reported. Options are close, wait or ignore. Default is close."). Default("close").IsSetByUser(&isZStopSet).String() // Data @@ -147,6 +197,10 @@ var ( format = kingpin.Flag("format", "Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary."). Short('O').Default("summary").PlaceHolder(" ").IsSetByUser(&isFormatSet).Enum("summary", "csv", "json", "pretty", "html", "influx-summary", "influx-details") + isSkipFirstSet = false + skipFirst = kingpin.Flag("skipFirst", "Skip the first X requests when doing the results tally."). + Default("0").IsSetByUser(&isSkipFirstSet).Uint() + // Connection isConnSet = false conns = kingpin.Flag("connections", "Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1."). @@ -360,7 +414,7 @@ func createConfigFromArgs(cfg *runner.Config) error { cfg.CName = *cname cfg.N = *n cfg.C = *c - cfg.QPS = *q + cfg.RPS = *rps cfg.Z = runner.Duration(*z) cfg.X = runner.Duration(*x) cfg.Timeout = runner.Duration(*t) @@ -384,6 +438,19 @@ func createConfigFromArgs(cfg *runner.Config) error { cfg.ReflectMetadata = rmdMap cfg.Debug = *debug cfg.EnableCompression = *enableCompression + cfg.LoadSchedule = *schedule + cfg.LoadStart = *loadStart + cfg.LoadStep = *loadStep + cfg.LoadEnd = *loadEnd + cfg.LoadStepDuration = runner.Duration(*loadStepDuration) + cfg.LoadMaxDuration = runner.Duration(*loadMaxDuration) + cfg.Async = *async + cfg.CSchedule = *cschdule + cfg.CStart = *cStart + cfg.CStep = *cstep + cfg.CEnd = *cEnd + cfg.CStepDuration = runner.Duration(*cStepDuration) + cfg.CMaxDuration = runner.Duration(*cMaxDuration) return nil } @@ -393,6 +460,8 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { return errors.New("config cannot be nil") } + // proto + if isProtoSet { dest.Proto = src.Proto } @@ -405,6 +474,8 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { dest.Call = src.Call } + // security + if isCACertSet { dest.RootCert = src.RootCert } @@ -437,18 +508,12 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { dest.CName = src.CName } + // run + if isNSet { dest.N = src.N } - if isCSet { - dest.C = src.C - } - - if isQSet { - dest.QPS = src.QPS - } - if isZSet { dest.Z = src.Z } @@ -465,6 +530,8 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { dest.ZStop = src.ZStop } + // data + if isDataSet { dest.Data = src.Data } @@ -489,6 +556,8 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { dest.MetadataPath = src.MetadataPath } + // other + if isSISet { dest.SI = src.SI } @@ -541,6 +610,70 @@ func mergeConfig(dest *runner.Config, src *runner.Config) error { dest.Host = src.Host } + // load + + if isAsyncSet { + dest.Async = src.Async + } + + if isRPSSet { + dest.RPS = src.RPS + } + + if isScheduleSet { + dest.LoadSchedule = src.LoadSchedule + } + + if isLoadStartSet { + dest.LoadStart = src.LoadStart + } + + if isLoadStepSet { + dest.LoadStep = src.LoadStep + } + + if isLoadEndSet { + dest.LoadEnd = src.LoadEnd + } + + if isLoadStepDurSet { + dest.LoadStepDuration = src.LoadStepDuration + } + + if isLoadMaxDurSet { + dest.LoadMaxDuration = src.LoadMaxDuration + } + + // concurrency + + if isCSet { + dest.C = src.C + } + + if isCScheduleSet { + dest.CSchedule = src.CSchedule + } + + if isCStartSet { + dest.CStart = src.CStart + } + + if isCStepSet { + dest.CStep = src.CStep + } + + if isCEndSet { + dest.CEnd = src.CEnd + } + + if isCStepDurSet { + dest.CStepDuration = src.CStepDuration + } + + if isCMaxDurSet { + dest.CMaxDuration = src.CMaxDuration + } + return nil } diff --git a/go.mod b/go.mod index e5e5ef35..19877148 100644 --- a/go.mod +++ b/go.mod @@ -3,38 +3,31 @@ module github.com/bojand/ghz go 1.14 require ( - cloud.google.com/go v0.46.3 // indirect github.com/alecthomas/kingpin v1.3.8-0.20191105203113-8c96d1c22481 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/bojand/hri v1.1.0 - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.16.0 // indirect github.com/go-playground/validator v9.30.0+incompatible github.com/gogo/protobuf v1.3.1 - github.com/golang/protobuf v1.3.2 + github.com/golang/protobuf v1.4.2 + github.com/golangci/golangci-lint v1.31.0 github.com/google/uuid v1.1.1 github.com/jhump/protoreflect v1.5.0 github.com/jinzhu/configor v1.1.1 github.com/jinzhu/gorm v1.9.11 - github.com/kr/text v0.2.0 // indirect github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 github.com/leodido/go-urn v1.2.0 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/pkg/errors v0.8.1 + github.com/mfridman/tparse v0.8.3 + github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.6 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 go.uber.org/multierr v1.3.0 go.uber.org/zap v1.13.0 - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b - golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e // indirect - golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 // indirect - google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect + golang.org/x/net v0.0.0-20200625001655-4c5254603344 + golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 + golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0 google.golang.org/grpc v1.24.0 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect - honnef.co/go/tools v0.0.1-2020.1.3 // indirect ) diff --git a/go.sum b/go.sum index 857e1173..2d8696a0 100644 --- a/go.sum +++ b/go.sum @@ -9,12 +9,23 @@ cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5 h1:XTrzB+F8+SpRmbhAH8HLxhiiG6nYNwaBZjrFps1oWEk= +github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/alecthomas/kingpin v1.3.8-0.20191105203113-8c96d1c22481 h1:NXM4vkjHeFp3bbp0z/u0AdQRLg6b5LrPeFwgjVHUW58= github.com/alecthomas/kingpin v1.3.8-0.20191105203113-8c96d1c22481/go.mod h1:b6br6/pDFSfMkBgC96TbpOji05q5pa+v5rIlS0Y6XtI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -22,42 +33,104 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bojand/hri v1.1.0 h1:OIv6AtbPjYv9A7qjUqylU11mbcP610JWsWCwvpc3w3U= github.com/bojand/hri v1.1.0/go.mod h1:qwGosuHpNn1S0nyw/mExN0+WZrDf4bQyWjhWh51y3VY= +github.com/bombsimon/wsl/v3 v3.1.0 h1:E5SRssoBgtVFPcYWUOFJEcgaySgdtTNYzsSKDOY7ss8= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daixiang0/gci v0.2.4 h1:BUCKk5nlK2m+kRIsoj+wb/5hazHvHeZieBKWd9Afa8Q= +github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUsNa8F+hHc6w= +github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.5.2 h1:3RJdgf6u4NZUumoP8nzbqiiNT8e1tC2Oc7jlgqre/IA= +github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/validator v9.30.0+incompatible h1:myhdWhx5UHvLXZ7maUcP0uYxyijMT+smaNAhARBVc9s= github.com/go-playground/validator v9.30.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -65,27 +138,104 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.31.0 h1:+m9I3LEmxXLpymkXRPkDQGzOVBmBYm16UtDiXqZxWek= +github.com/golangci/golangci-lint v1.31.0/go.mod h1:aMQuNCA+NDU5+4jLL5pEuFHoue0IznKE2+/GsFvvs8A= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3 h1:iwp+5/UAyzQSFgQ4uR2sni99sJ8Eo9DEacKWM5pekIg= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.5.0 h1:NgpVT+dX71c8hZnxHof2M7QDK7QtohIJ7DYycjnkyfc= github.com/jhump/protoreflect v1.5.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jinzhu/configor v1.1.1 h1:gntDP+ffGhs7aJ0u8JvjCDts2OsxsI7bnz3q+jC+hSY= github.com/jinzhu/configor v1.1.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= @@ -94,93 +244,267 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyoh86/exportloopref v0.1.7 h1:u+iHuTbkbTS2D/JP7fCuZDo/t3rBVGo3Hf58Rc+lQVY= +github.com/kyoh86/exportloopref v0.1.7/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mfridman/tparse v0.8.3 h1:DnjEnBXdlUJPo8ShfNPasu7m52iI1ETiST5RvS6b0c4= +github.com/mfridman/tparse v0.8.3/go.mod h1:LzZWLkqcQrOfgvqZn7LOSBzgZwWnqI5NQsfgQVOT1o8= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0 h1:eMV1t2NQRc3r1k3guWiv/zEeqZZP6kPvpUfy6byfL1g= +github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.2.0 h1:UOVMyH2EKkxIfzrULvA9n/tO+HtEhqD9mrLSWMr5FwU= +github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.1.0 h1:DWbye9KyMgytn8uYpuHkwf0RHqAYO6Ay/D0TbCpPtVU= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= +github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec/v2 v2.4.0 h1:ivAoWcY5DMs9n04Abc1VkqZBO0FL0h4ShTcVsC53lCE= +github.com/securego/gosec/v2 v2.4.0/go.mod h1:0/Q4cjmlFDfDUj1+Fib61sc+U5IQb2w+Iv9/C3wPVko= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= +github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sourcegraph/go-diff v0.6.0 h1:WbN9e/jD8ujU+o0vd9IFN5AEwtfB0rn/zM/AANaClqQ= +github.com/sourcegraph/go-diff v0.6.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/ssgreg/nlreturn/v2 v2.1.0 h1:6/s4Rc49L6Uo6RLjhWZGBpWWjfzk2yrf1nIW8m4wgVA= +github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tetafro/godot v0.4.8 h1:h61+hQraWhdI6WYqMwAwZYCE5yxL6a9/Orw4REbabSU= +github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -195,13 +519,19 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -209,10 +539,15 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -221,60 +556,101 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJtiZOsjU9RgslJGhn95XE= -golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0 h1:SQvH+DjrwqD1hyyQU+K7JegHz1KEZgEwt17p9d6R2eg= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -296,29 +672,56 @@ google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEd google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f h1:gi7cb8HTDZ6q8VqsUpkdoFi3vxwHMneQ6+Q5Ap5hjPE= +mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/helloworld/greeter_server.go b/internal/helloworld/greeter_server.go index 7b09323a..73ec43fb 100644 --- a/internal/helloworld/greeter_server.go +++ b/internal/helloworld/greeter_server.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math/rand" + "strings" "sync" "time" @@ -220,6 +221,37 @@ func (c *HWStatsHandler) GetConnectionCount() int { return val } +// GetCountByWorker gets count of requests by goroutine +func (s *Greeter) GetCountByWorker(key CallType) map[string]int { + s.mutex.Lock() + val, ok := s.calls[key] + s.mutex.Unlock() + + if !ok { + return nil + } + + counts := make(map[string]int) + + for _, reqs := range val { + for _, req := range reqs { + name := req.GetName() + if strings.Contains(name, "worker:") { + parts := strings.Split(name, ":") + wid := parts[len(parts)-1] + wc, ok := counts[wid] + if !ok { + counts[wid] = 0 + } + + counts[wid] = wc + 1 + } + } + } + + return counts +} + // HandleConn handle the connection func (c *HWStatsHandler) HandleConn(ctx context.Context, cs stats.ConnStats) { // no-op diff --git a/load/pacer.go b/load/pacer.go new file mode 100644 index 00000000..325a9527 --- /dev/null +++ b/load/pacer.go @@ -0,0 +1,319 @@ +package load + +import ( + "fmt" + "math" + "sync" + "time" +) + +// nano is the const for number of nanoseconds in a second +const nano = 1e9 + +// Pacer defines the interface to control the rate of hit. +type Pacer interface { + // Pace returns the duration the attacker should wait until + // making next hit, given an already elapsed duration and + // completed hits. If the second return value is true, an attacker + // should stop sending hits. + Pace(elapsed time.Duration, hits uint64) (wait time.Duration, stop bool) + + // Rate returns a Pacer's instantaneous hit rate (per seconds) + // at the given elapsed duration of an attack. + Rate(elapsed time.Duration) float64 +} + +// A ConstantPacer defines a constant rate of hits. +type ConstantPacer struct { + Freq uint64 // Frequency of hits per second + Max uint64 // Optional maximum allowed hits +} + +// String returns a pretty-printed description of the ConstantPacer's behaviour: +// ConstantPacer{Freq: 1} => Constant{1 hits / 1s} +func (cp *ConstantPacer) String() string { + return fmt.Sprintf("Constant{%d hits / 1s}", cp.Freq) +} + +// Pace determines the length of time to sleep until the next hit is sent. +func (cp *ConstantPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, bool) { + + if cp.Max > 0 && hits >= cp.Max { + return 0, true + } + + if cp.Freq == 0 { + return 0, false // Zero value = infinite rate + } + + expectedHits := uint64(cp.Freq) * uint64(elapsed/nano) + if hits < expectedHits { + // Running behind, send next hit immediately. + return 0, false + } + + interval := uint64(nano / int64(cp.Freq)) + if math.MaxInt64/interval < hits { + // We would overflow delta if we continued, so stop the attack. + return 0, true + } + + delta := time.Duration((hits + 1) * interval) + // Zero or negative durations cause time.Sleep to return immediately. + return delta - elapsed, false +} + +// Rate returns a ConstantPacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration of an attack. Since it's constant, the return +// value is independent of the given elapsed duration. +func (cp *ConstantPacer) Rate(elapsed time.Duration) float64 { + return cp.hitsPerNs() * 1e9 +} + +// hitsPerNs returns the rate in fractional hits per nanosecond. +func (cp *ConstantPacer) hitsPerNs() float64 { + return float64(cp.Freq) / nano +} + +// StepPacer paces an attack by starting at a given request rate +// and increasing or decreasing with steps at a given step interval and duration. +type StepPacer struct { + Start ConstantPacer // Constant start rate + Step int64 // Step value + StepDuration time.Duration // Step duration + Stop ConstantPacer // Optional constant stop value + LoadDuration time.Duration // Optional maximum load duration + Max uint64 // Optional maximum allowed hits + + once sync.Once + init bool // TOOO improve this + constAt time.Duration + baseHits uint64 +} + +func (p *StepPacer) initialize() { + + if p.StepDuration == 0 { + panic("StepPacer.StepDuration cannot be 0") + } + + if p.Step == 0 { + panic("StepPacer.Step cannot be 0") + } + + if p.Start.Freq == 0 { + panic("Start.Freq cannot be 0") + } + + if p.init { + return + } + + p.init = true + + if p.LoadDuration > 0 { + p.constAt = p.LoadDuration + + if p.Stop.Freq == 0 { + steps := p.constAt.Nanoseconds() / p.StepDuration.Nanoseconds() + + p.Stop.Freq = p.Start.Freq + uint64(int64(p.Step)*steps) + } + } else if p.Stop.Freq > 0 && p.constAt == 0 { + stopRPS := float64(p.Stop.Freq) + + if p.Step > 0 { + t := time.Duration(0) + for { + if p.Rate(t) > stopRPS { + p.constAt = t + break + } + t = t + p.StepDuration + } + } else { + t := time.Duration(0) + for { + if p.Rate(t) < stopRPS { + p.constAt = t + break + } + t = t + p.StepDuration + } + } + } + + if p.constAt > 0 { + p.baseHits = uint64(p.hits(p.constAt)) + } +} + +// Pace determines the length of time to sleep until the next hit is sent. +func (p *StepPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, bool) { + + if p.Max > 0 && hits >= p.Max { + return 0, true + } + + p.once.Do(p.initialize) + + expectedHits := p.hits(elapsed) + + if hits < uint64(expectedHits) { + // Running behind, send next hit immediately. + return 0, false + } + + // const part + if p.constAt > 0 && elapsed >= p.constAt { + if p.Stop.Freq == 0 { + return 0, true + } + + return p.Stop.Pace(elapsed-p.constAt, hits-p.baseHits) + } + + rate := p.Rate(elapsed) + interval := nano / rate + + if n := uint64(interval); n != 0 && math.MaxInt64/n < hits { + // We would overflow wait if we continued, so stop the attack. + return 0, true + } + + delta := float64(hits+1) - expectedHits + wait := time.Duration(interval * delta) + + // if wait > nano { + // intervals := elapsed / nano + // wait = (intervals+1)*nano - elapsed + // } + + return wait, false +} + +// Rate returns a StepPacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration. +func (p *StepPacer) Rate(elapsed time.Duration) float64 { + p.initialize() + + t := elapsed + + if p.constAt > 0 && elapsed >= p.constAt { + return float64(p.Stop.Freq) + } + + steps := t.Nanoseconds() / p.StepDuration.Nanoseconds() + + rate := (p.Start.hitsPerNs() + float64(int64(p.Step)*steps)/nano) * 1e9 + + if rate < 0 { + rate = 0 + } + + return rate +} + +// hits returns the number of hits that have been sent at elapsed duration t. +func (p *StepPacer) hits(t time.Duration) float64 { + if t < 0 { + return 0 + } + + steps := t.Nanoseconds() / p.StepDuration.Nanoseconds() + + base := p.Start.hitsPerNs() * 1e9 + + // first step + var s float64 + if steps > 0 { + s = p.StepDuration.Seconds() * base + } else { + s = t.Seconds() * base + } + + // previous steps: 1...n + for i := int64(1); i < steps; i++ { + d := time.Duration(p.StepDuration.Nanoseconds() * i) + r := p.Rate(d) + ch := r * p.StepDuration.Seconds() + s = s + ch + } + + c := float64(0) + if steps > 0 { + // current step + elapsed := time.Duration(t.Nanoseconds() - steps*p.StepDuration.Nanoseconds()) + c = elapsed.Seconds() * p.Rate(t) + } + + return s + c +} + +// String returns a pretty-printed description of the StepPacer's behaviour: +// StepPacer{Step: 1, StepDuration: 5s} => Step{Step:1 hits / 5s} +func (p *StepPacer) String() string { + return fmt.Sprintf("Step{Step: %d hits / %s}", p.Step, p.StepDuration.String()) +} + +// LinearPacer paces the hit rate by starting at a given request rate +// and increasing linearly with the given slope at 1s interval. +type LinearPacer struct { + Start ConstantPacer // Constant start rate + Slope int64 // Slope value to change the rate + Stop ConstantPacer // Constant stop rate + LoadDuration time.Duration // Total maximum load duration + Max uint64 // Maximum number of hits + + once sync.Once + sp StepPacer +} + +// initializes the wrapped step pacer +func (p *LinearPacer) initialize() { + if p.Start.Freq == 0 { + panic("LinearPacer.Start cannot be 0") + } + + if p.Slope == 0 { + panic("LinearPacer.Slope cannot be 0") + } + + p.once.Do(func() { + p.sp = StepPacer{ + Start: p.Start, + Step: p.Slope, + StepDuration: time.Second, + Stop: p.Stop, + LoadDuration: p.LoadDuration, + } + + p.sp.initialize() + }) +} + +// Pace determines the length of time to sleep until the next hit is sent. +func (p *LinearPacer) Pace(elapsed time.Duration, hits uint64) (time.Duration, bool) { + if p.Max > 0 && hits >= p.Max { + return 0, true + } + + p.initialize() + + return p.sp.Pace(elapsed, hits) +} + +// Rate returns a LinearPacer's instantaneous hit rate (i.e. requests per second) +// at the given elapsed duration. +func (p *LinearPacer) Rate(elapsed time.Duration) float64 { + + p.initialize() + + return p.sp.Rate(elapsed) +} + +// String returns a pretty-printed description of the LinearPacer's behaviour: +// LinearPacer{Slope: 1} => Linear{1 hits / 1s} +func (p *LinearPacer) String() string { + return fmt.Sprintf("Linear{%d hits / 1s}", p.Slope) +} diff --git a/load/pacer_test.go b/load/pacer_test.go new file mode 100644 index 00000000..d9350abd --- /dev/null +++ b/load/pacer_test.go @@ -0,0 +1,1081 @@ +package load + +import ( + "math" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestConstantPacer(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + freq uint64 + max uint64 + elapsed time.Duration + hits uint64 + wait time.Duration + stop bool + }{ + // 1 hit/sec, 0 hits sent, 0s elapsed => 1s until next hit + { + freq: 1, + elapsed: 0, + hits: 0, + wait: 1000000000, + stop: false, + }, + // 1 hit/sec, 0 hits sent, 0.1s elapsed => 0.9s until next hit + { + freq: 1, + elapsed: 100 * time.Millisecond, + hits: 0, + wait: 900 * time.Millisecond, + stop: false, + }, + // 1 hit/sec, 0 hits sent, 1s elapsed => 0s until next hit + { + freq: 1, + elapsed: 1 * time.Second, + hits: 0, + wait: 0, + stop: false, + }, + // 1 hit/sec, 0 hits sent, 2s elapsed => 0s (-1s) until next hit + { + freq: 1, + elapsed: 2 * time.Second, + hits: 0, + wait: 0, + stop: false, + }, + // 1 hit/sec, 1 hit sent, 1s elapsed => 1s until next hit + { + freq: 1, + elapsed: 1 * time.Second, + hits: 1, + wait: 1 * time.Second, + stop: false, + }, + // 1 hit/sec, 2 hits sent, 1s elapsed => 2s until next hit + { + freq: 1, + elapsed: 1 * time.Second, + hits: 2, + wait: 2 * time.Second, + stop: false, + }, + // 1 hit/sec, 10 hits sent, 1s elapsed => 10s until next hit + { + freq: 1, + elapsed: 1 * time.Second, + hits: 10, + wait: 10 * time.Second, + stop: false, + }, + // 1 hit/sec, 10 hits sent, 11s elapsed => 0s until next hit + { + freq: 1, + elapsed: 11 * time.Second, + hits: 10, + wait: 0, + stop: false, + }, + // 2 hit/sec, 9 hits sent, 4.9s elapsed => 100ms until next hit + { + freq: 2, + elapsed: 4900 * time.Millisecond, + hits: 9, + wait: 100 * time.Millisecond, + stop: false, + }, + // BAD TESTS + // Zero frequency. + { + freq: 0, + elapsed: 0, + hits: 0, + wait: 0, + stop: false, + }, + // Large hits, overflow int64. + { + freq: 1, + elapsed: time.Duration(math.MaxInt64), + hits: 2562048, + wait: 0, + stop: false, + }, + // Max + { + freq: 1, + elapsed: 1 * time.Second, + hits: 10, + wait: 10 * time.Second, + stop: false, + max: 0, + }, + { + freq: 1, + elapsed: 1 * time.Second, + hits: 10, + wait: 0, + stop: true, + max: 7, + }, + } { + cp := ConstantPacer{Freq: tc.freq, Max: tc.max} + wait, stop := cp.Pace(tc.elapsed, tc.hits) + assert.Equal(t, tc.wait, wait) + assert.Equal(t, tc.stop, stop) + } +} + +func TestConstantPacer_Rate(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + freq uint64 + elapsed time.Duration + rate float64 + }{ + { + freq: 60, + elapsed: 0, + rate: 60, + }, + { + freq: 500, + elapsed: 5 * time.Second, + rate: 500.0, + }, + } { + cp := ConstantPacer{Freq: tc.freq} + actual, expected := cp.Rate(tc.elapsed), tc.rate + assert.True(t, floatEqual(actual, expected), "%s.Rate(_): actual %f, expected %f", cp, actual, expected) + } +} + +func TestConstantPacer_String(t *testing.T) { + cp := ConstantPacer{Freq: 5} + actual := cp.String() + assert.Equal(t, "Constant{5 hits / 1s}", actual) +} + +func TestLinearPacer(t *testing.T) { + + t.Parallel() + + for ti, tt := range []struct { + // pacer config + start uint64 + slope int64 + stopDuration time.Duration + stopRate uint64 + // params + elapsed time.Duration + hits uint64 + // expected + wait time.Duration + stop bool + }{ + // slope: 1, start 1 + { + start: 1, + slope: 1, + elapsed: 0, + hits: 0, + wait: 1 * time.Second, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 1 * time.Second, + hits: 0, + wait: 0 * time.Millisecond, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 0, + hits: 1, + wait: 2 * time.Second, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 1 * time.Second, + hits: 1, + wait: 500 * time.Millisecond, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 1 * time.Second, + hits: 2, + wait: 1000 * time.Millisecond, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 2 * time.Second, + hits: 2, + wait: 0 * time.Millisecond, + stop: false, + }, + { + start: 1, + slope: 1, + elapsed: 2500 * time.Millisecond, + hits: 5, + wait: 500 * time.Millisecond, + stop: false, + }, + // slope: 1, start 5 + { + start: 5, + slope: 1, + elapsed: 0, + hits: 0, + wait: 200 * time.Millisecond, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 1 * time.Second, + hits: 5, + wait: 166666 * time.Microsecond, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 1200 * time.Millisecond, + hits: 5, + wait: 0 * time.Microsecond, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 2000 * time.Millisecond, + hits: 6, + wait: 0, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 2000 * time.Millisecond, + hits: 7, + wait: 0, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 2000 * time.Millisecond, + hits: 11, + wait: 142857 * time.Microsecond, + stop: false, + }, + { + start: 5, + slope: 1, + elapsed: 2000 * time.Millisecond, + hits: 12, + wait: 285714 * time.Microsecond, + stop: false, + }, + // // slope: -1, start 20, various elapsed and hits + { + start: 20, + slope: -1, + elapsed: 0, + hits: 0, + wait: 50 * time.Millisecond, + stop: false, + }, + { + start: 20, + slope: -1, + elapsed: 1100 * time.Millisecond, + hits: 0, + wait: 0, + stop: false, + }, + { + start: 20, + slope: -1, + elapsed: 50 * time.Millisecond, + hits: 1, + wait: 50 * time.Millisecond, + stop: false, + }, + { + start: 20, + slope: -1, + elapsed: 50 * time.Millisecond, + hits: 19, + wait: 950 * time.Millisecond, + stop: false, + }, + { + start: 20, + slope: -1, + elapsed: 950 * time.Millisecond, + hits: 19, + wait: 50 * time.Millisecond, + stop: false, + }, + // slope: 1, stop rate + { + start: 1, + slope: 1, + elapsed: 0, + stopRate: 20, + hits: 0, + wait: 1 * time.Second, + stop: false, + }, + { + start: 1, + slope: 1, + stopRate: 5, + elapsed: 5 * time.Second, + hits: 0, + wait: 0, + stop: false, + }, + { + start: 1, + slope: 1, + stopRate: 5, + elapsed: 5000 * time.Millisecond, + hits: 17, + wait: 600 * time.Millisecond, + stop: false, + }, + // slope: 1, stop duration + { + start: 1, + slope: 1, + elapsed: 0, + stopDuration: 5 * time.Second, + hits: 0, + wait: 1 * time.Second, + stop: false, + }, + { + start: 1, + slope: 1, + stopDuration: 5 * time.Second, + elapsed: 2 * time.Second, + hits: 0, + wait: 0, + stop: false, + }, + { + start: 1, + slope: 1, + stopDuration: 5 * time.Second, + elapsed: 2000 * time.Millisecond, + hits: 5, + wait: 1 * time.Second, + stop: false, + }, + { + start: 1, + slope: 1, + stopDuration: 5 * time.Second, + elapsed: 5200 * time.Millisecond, + hits: 18, + wait: 466666 * time.Microsecond, + stop: false, + }, + } { + t.Run(strconv.Itoa(ti), func(t *testing.T) { + p := LinearPacer{ + Start: ConstantPacer{Freq: tt.start}, + Slope: tt.slope, + Stop: ConstantPacer{Freq: tt.stopRate}, + LoadDuration: tt.stopDuration, + } + + wait, stop := p.Pace(tt.elapsed, tt.hits) + + assert.True(t, durationEqual(tt.wait, wait), + "%d: %+v.Pace(%s, %d) = (%s, %t); expected (%s, %t)", ti, &p, tt.elapsed, tt.hits, wait, stop, tt.wait, tt.stop) + + assert.Equal(t, tt.stop, stop) + }) + } +} + +func TestStepPacer_hits(t *testing.T) { + t.Parallel() + + // TODO improve this to have different pacer params + p := StepPacer{ + Start: ConstantPacer{Freq: 10}, + StepDuration: 4 * time.Second, + Step: 10, + } + + for _, tc := range []struct { + elapsed time.Duration + hits float64 + }{ + {0, 0}, + {1 * time.Second, 10}, + {2 * time.Second, 20}, + {6 * time.Second, 80}, + } { + actual := p.hits(tc.elapsed) + expected := tc.hits + + assert.True(t, floatEqual(actual, expected), "%+v.hits(%v) = %v, expected: %v", p, tc.elapsed, actual, expected) + } +} + +func TestStepPacer_Rate(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + // pacer config + start uint64 + step int64 + stepDuration time.Duration + stop uint64 + stopDuration time.Duration + // params + elapsed time.Duration + // expected + rate float64 + }{ + // step: 5, start: 1 + { + start: 1, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 0, + rate: 1, + }, + { + start: 1, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 1 * time.Second, + rate: 1, + }, + { + start: 1, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 3 * time.Second, + rate: 1, + }, + { + start: 1, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 4 * time.Second, + rate: 6, + }, + { + start: 1, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 5 * time.Second, + rate: 6, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 25, + stopDuration: 0, + elapsed: 9 * time.Second, + rate: 15, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 25, + stopDuration: 0, + elapsed: 12 * time.Second, + rate: 20, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 25, + stopDuration: 0, + elapsed: 22 * time.Second, + rate: 25, + }, + // start: 5, step: 5, stop duration: 25s + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 0, + rate: 5, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 19 * time.Second, + rate: 25, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 20 * time.Second, + rate: 30, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 21 * time.Second, + rate: 30, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 26 * time.Second, + rate: 35, + }, + { + start: 5, + step: 5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 25 * time.Second, + elapsed: 31 * time.Second, + rate: 35, + }, + // start: 15, step -5 + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 0, + rate: 15, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 3 * time.Second, + rate: 15, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 4 * time.Second, + rate: 10, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 5 * time.Second, + rate: 10, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 11 * time.Second, + rate: 5, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 12 * time.Second, + rate: 0, + }, + { + start: 15, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 17 * time.Second, + rate: 0, + }, + // start: 20, step: -5, stop: 5 + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 5, + stopDuration: 0, + elapsed: 0, + rate: 20, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 5, + stopDuration: 0, + elapsed: 11 * time.Second, + rate: 10, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 5, + stopDuration: 0, + elapsed: 12 * time.Second, + rate: 5, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 5, + stopDuration: 0, + elapsed: 13 * time.Second, + rate: 5, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 5, + stopDuration: 0, + elapsed: 22 * time.Second, + rate: 5, + }, + // start: 20, step: -5, stop: 10s + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 0, + rate: 20, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 9 * time.Second, + rate: 10, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 10 * time.Second, + rate: 10, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 11 * time.Second, + rate: 10, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 15 * time.Second, + rate: 10, + }, + { + start: 20, + step: -5, + stepDuration: 4 * time.Second, + stop: 0, + stopDuration: 10 * time.Second, + elapsed: 22 * time.Second, + rate: 10, + }, + } { + p := StepPacer{ + Start: ConstantPacer{Freq: tc.start}, + Step: tc.step, StepDuration: tc.stepDuration, + LoadDuration: tc.stopDuration, Stop: ConstantPacer{Freq: tc.stop}} + + actual := p.Rate(tc.elapsed) + expected := tc.rate + + assert.True(t, floatEqual(actual, expected), "%+v.Rate(%v) = %v, expected: %v", p, tc.elapsed, actual, expected) + } +} + +func TestStepPacer(t *testing.T) { + t.Parallel() + + for ti, tc := range []struct { + // pacer config + start uint64 + step int64 + stepDuration time.Duration + stop uint64 + stopDuration time.Duration + max uint64 + // params + elapsed time.Duration + hits uint64 + // expected + wait time.Duration + stopResult bool + }{ + // start: 5, step: 5 + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0 * time.Second, + elapsed: 0 * time.Second, + hits: 0, + wait: 200 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0 * time.Second, + elapsed: 1 * time.Second, + hits: 4, + wait: 0 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0 * time.Second, + elapsed: 1 * time.Second, + hits: 6, + wait: 400 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0 * time.Second, + elapsed: 4200 * time.Millisecond, + hits: 25, + wait: 1 * time.Second, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0 * time.Second, + elapsed: 5000 * time.Millisecond, + hits: 25, + wait: 100 * time.Millisecond, + stopResult: false, + }, + // start: 5, step: 5, stop: 25 + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + stopDuration: 0 * time.Second, + elapsed: 5000 * time.Millisecond, + hits: 25, + wait: 100 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + stopDuration: 0 * time.Second, + elapsed: 20 * time.Second, + hits: 250, + wait: 40 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + stopDuration: 0 * time.Second, + elapsed: 30 * time.Second, + hits: 450, + wait: 0 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + stopDuration: 0 * time.Second, + elapsed: 30 * time.Second, + hits: 500, + wait: 40 * time.Millisecond, + stopResult: false, + }, + // start: 5, step: 5, stop: 20s + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 5000 * time.Millisecond, + hits: 25, + wait: 100 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 19 * time.Second, + hits: 25, + wait: 0 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 20 * time.Second, + hits: 250, + wait: 40 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 30 * time.Second, + hits: 400, + wait: 0 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 30 * time.Second, + hits: 500, + wait: 40 * time.Millisecond, + stopResult: false, + }, + // start: 20, step: -5, + { + start: 20, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 0 * time.Millisecond, + hits: 0, + wait: 50 * time.Millisecond, + stopResult: false, + }, + { + start: 20, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 5000 * time.Millisecond, + hits: 100, + wait: 66666666 * time.Nanosecond, + stopResult: false, + }, + { + start: 20, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 5000 * time.Millisecond, + hits: 100, + wait: 66666666 * time.Nanosecond, + stopResult: false, + }, + { + start: 20, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 20 * time.Second, + hits: 249, + wait: 0, + stopResult: false, + }, + { + start: 20, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 0, + elapsed: 20 * time.Second, + hits: 250, + wait: 0, + stopResult: true, + }, + { + start: 30, + step: -5, + stepDuration: 5 * time.Second, + stop: 0, + stopDuration: 20 * time.Second, + elapsed: 30 * time.Second, + hits: 550, + wait: 100 * time.Millisecond, + stopResult: false, + }, + // Max + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + stopDuration: 0 * time.Second, + max: 100, + elapsed: 5000 * time.Millisecond, + hits: 25, + wait: 100 * time.Millisecond, + stopResult: false, + }, + { + start: 5, + step: 5, + stepDuration: 5 * time.Second, + stop: 25, + max: 10, + stopDuration: 0 * time.Second, + elapsed: 5000 * time.Millisecond, + hits: 25, + wait: 0, + stopResult: true, + }, + } { + t.Run(strconv.Itoa(ti), func(t *testing.T) { + p := StepPacer{ + Start: ConstantPacer{Freq: tc.start, Max: tc.max}, + Max: tc.max, + Step: tc.step, StepDuration: tc.stepDuration, + LoadDuration: tc.stopDuration, Stop: ConstantPacer{Freq: tc.stop}} + + wait, stop := p.Pace(tc.elapsed, tc.hits) + + assert.Equal(t, tc.wait, wait, "%+v.Pace(%v, %v) = %v, expected: %v", p, tc.elapsed, tc.hits, wait, tc.wait) + assert.Equal(t, tc.stopResult, stop, "%+v.Pace(%v, %v) = %v, expected: %v", p, tc.elapsed, tc.hits, stop, tc.stopResult) + }) + } +} + +func TestStepPacer_String(t *testing.T) { + p := StepPacer{ + Start: ConstantPacer{Freq: 5, Max: 100}, + Max: 100, + Step: 2, StepDuration: 5 * time.Second, + LoadDuration: 25 * time.Second, Stop: ConstantPacer{Freq: 25}} + + actual := p.String() + assert.Equal(t, "Step{Step: 2 hits / 5s}", actual) +} + +// Stolen from https://github.com/google/go-cmp/cmp/cmpopts/equate.go +// to avoid an unwieldy dependency. Both fraction and margin set at 1e-6. +func floatEqual(x, y float64) bool { + relMarg := 1e-6 * math.Min(math.Abs(x), math.Abs(y)) + return math.Abs(x-y) <= math.Max(1e-6, relMarg) +} + +// A similar function to the above because SinePacer.Pace has discrete +// inputs and outputs but uses floats internally, and sometimes the +// floating point imprecision leaks out :-( +func durationEqual(x, y time.Duration) bool { + diff := x - y + if diff < 0 { + diff = -diff + } + return diff <= time.Microsecond +} diff --git a/load/worker_ticker.go b/load/worker_ticker.go new file mode 100644 index 00000000..d72f69ce --- /dev/null +++ b/load/worker_ticker.go @@ -0,0 +1,159 @@ +package load + +import ( + "time" +) + +// WorkerTicker is the interface controlling worker parallelism. +type WorkerTicker interface { + // Ticker returns a channel which sends TickValues + // When a value is received the number of workers should be appropriately + // increased or decreased given by the delta property. + Ticker() <-chan TickValue + + // Run starts the worker ticker + Run() + + // Finish closes the channel + Finish() +} + +// TickValue is the tick value sent over the ticker channel. +type TickValue struct { + Delta int // Delta value representing worker increase or decrease + Done bool // A flag representing whether the ticker is done running. Once true no more values should be received over the ticker channel. +} + +// ConstWorkerTicker represents a constant number of workers. +// It would send one value for initial number of workers to start. +type ConstWorkerTicker struct { + C chan TickValue // The tick value channel + N uint // The number of workers +} + +// Ticker returns the ticker channel. +func (c *ConstWorkerTicker) Ticker() <-chan TickValue { + return c.C +} + +// Run runs the ticker. +func (c *ConstWorkerTicker) Run() { + c.C <- TickValue{Delta: int(c.N), Done: true} +} + +// Finish closes the channel. +func (c *ConstWorkerTicker) Finish() { + close(c.C) +} + +// StepWorkerTicker is the worker ticker that implements step adjustments to worker concurrency. +type StepWorkerTicker struct { + C chan TickValue // The tick value channel + + Start uint // Starting number of workers + Step int // Step change + StepDuration time.Duration // Duration to apply the step change + Stop uint // Final number of workers + MaxDuration time.Duration // Maximum duration +} + +// Ticker returns the ticker channel. +func (c *StepWorkerTicker) Ticker() <-chan TickValue { + return c.C +} + +// Run runs the ticker. +func (c *StepWorkerTicker) Run() { + + stepUp := c.Step > 0 + wc := int(c.Start) + done := make(chan bool) + + ticker := time.NewTicker(c.StepDuration) + defer ticker.Stop() + + begin := time.Now() + + c.C <- TickValue{Delta: int(c.Start)} + + go func() { + for range ticker.C { + // we have load duration and we eclipsed it + if c.MaxDuration > 0 && time.Since(begin) >= c.MaxDuration { + if stepUp && c.Stop > 0 && c.Stop >= uint(wc) { + // if we have step up and stop value is > current count + // send the final diff + c.C <- TickValue{Delta: int(c.Stop - uint(wc)), Done: true} + } else if !stepUp && c.Stop > 0 && c.Stop <= uint(wc) { + // if we have step down and stop value is < current count + // send the final diff + c.C <- TickValue{Delta: int(c.Stop - uint(wc)), Done: true} + } else { + // send done signal + c.C <- TickValue{Delta: 0, Done: true} + } + + done <- true + return + } else if (c.MaxDuration == 0) && ((c.Stop > 0 && stepUp && wc >= int(c.Stop)) || + (!stepUp && wc <= int(c.Stop))) { + // we do not have load duration + // if we have stop and are step up and current count >= stop + // or if we have stop and are step down and current count <= stop + // send done signal + + c.C <- TickValue{Delta: 0, Done: true} + done <- true + return + } else { + c.C <- TickValue{Delta: c.Step} + wc = wc + c.Step + } + } + }() + + <-done +} + +// Finish closes the channel. +func (c *StepWorkerTicker) Finish() { + close(c.C) +} + +// LineWorkerTicker is the worker ticker that implements line adjustments to concurrency. +// Essentially this is same as step worker with 1s step duration. +type LineWorkerTicker struct { + C chan TickValue // The tick value channel + + Start uint // Starting number of workers + Slope int // Slope value to adjust the number of workers + Stop uint // Final number of workers + MaxDuration time.Duration // Maximum adjustment duration + + stepTicker StepWorkerTicker +} + +// Ticker returns the ticker channel. +func (c *LineWorkerTicker) Ticker() <-chan TickValue { + return c.C +} + +// Run runs the ticker. +func (c *LineWorkerTicker) Run() { + + c.stepTicker = StepWorkerTicker{ + C: c.C, + Start: c.Start, + Step: c.Slope, + StepDuration: 1 * time.Second, + Stop: c.Stop, + MaxDuration: c.MaxDuration, + } + + c.stepTicker.Run() +} + +// Finish closes the internal tick value channel. +func (c *LineWorkerTicker) Finish() { + c.stepTicker.Finish() +} diff --git a/load/worker_ticker_test.go b/load/worker_ticker_test.go new file mode 100644 index 00000000..fe61e797 --- /dev/null +++ b/load/worker_ticker_test.go @@ -0,0 +1,273 @@ +package load + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestConstWorkerTicker(t *testing.T) { + wt := ConstWorkerTicker{N: 5, C: make(chan TickValue)} + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + + assert.NotEmpty(t, tv) + assert.Equal(t, 5, tv.Delta) +} + +func TestStepWorkerTicker(t *testing.T) { + t.Parallel() + + t.Run("step increase load duration", func(t *testing.T) { + wt := StepWorkerTicker{ + C: make(chan TickValue), + Start: 5, + Step: 2, + Stop: 0, + StepDuration: 2 * time.Second, + MaxDuration: 5 * time.Second, + } + + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + assert.NotEmpty(t, tv) + assert.Equal(t, 5, tv.Delta) + assert.False(t, tv.Done) + + start := time.Now() + tv = <-wct + end := time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, 2, tv.Delta) + assert.False(t, tv.Done) + expected := 2 * time.Second + assert.True(t, durationEqual(expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, 2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(2*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + assert.Equal(t, 0, tv.Delta) + assert.True(t, tv.Done) + }) + + t.Run("step increase load duration with stop", func(t *testing.T) { + wt := StepWorkerTicker{ + C: make(chan TickValue), + Start: 5, + Step: 2, + Stop: 15, + StepDuration: 2 * time.Second, + MaxDuration: 5 * time.Second, + } + + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + assert.NotEmpty(t, tv) + assert.Equal(t, 5, tv.Delta) + assert.False(t, tv.Done) + + start := time.Now() + tv = <-wct + end := time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, 2, tv.Delta) + assert.False(t, tv.Done) + expected := 2 * time.Second + assert.True(t, durationEqual(expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, 2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(2*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.Equal(t, 6, tv.Delta) + assert.True(t, tv.Done) + assert.True(t, durationEqual(3*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + }) + + t.Run("step decrease load duration", func(t *testing.T) { + wt := StepWorkerTicker{ + C: make(chan TickValue), + Start: 10, + Step: -2, + Stop: 0, + StepDuration: 2 * time.Second, + MaxDuration: 5 * time.Second, + } + + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + assert.NotEmpty(t, tv) + assert.Equal(t, 10, tv.Delta) + assert.False(t, tv.Done) + + start := time.Now() + tv = <-wct + end := time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + expected := 2 * time.Second + assert.True(t, durationEqual(expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(2*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + assert.Equal(t, 0, tv.Delta) + assert.True(t, tv.Done) + }) + + t.Run("step decrease with stop", func(t *testing.T) { + wt := StepWorkerTicker{ + C: make(chan TickValue), + Start: 10, + Step: -2, + Stop: 4, + StepDuration: 2 * time.Second, + MaxDuration: 0, + } + + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + assert.NotEmpty(t, tv) + assert.Equal(t, 10, tv.Delta) + assert.False(t, tv.Done) + + start := time.Now() + tv = <-wct + end := time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + expected := 2 * time.Second + assert.True(t, durationEqual(expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(2*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(3*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.Equal(t, 0, tv.Delta) + assert.True(t, tv.Done) + assert.True(t, durationEqual(4*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + }) + + t.Run("step decrease with stop and load duration", func(t *testing.T) { + + wt := StepWorkerTicker{ + C: make(chan TickValue), + Start: 12, + Step: -2, + Stop: 3, + StepDuration: 2 * time.Second, + MaxDuration: 5 * time.Second, + } + + defer wt.Finish() + + wct := wt.Ticker() + + assert.NotNil(t, wct) + + go func() { + wt.Run() + }() + + tv := <-wct + assert.NotEmpty(t, tv) + assert.Equal(t, 12, tv.Delta) + assert.False(t, tv.Done) + + start := time.Now() + tv = <-wct + end := time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + expected := 2 * time.Second + assert.True(t, durationEqual(expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.NotEmpty(t, tv) + assert.Equal(t, -2, tv.Delta) + assert.False(t, tv.Done) + assert.True(t, durationEqual(2*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + + tv = <-wct + end = time.Since(start) + assert.Equal(t, -5, tv.Delta) + assert.True(t, tv.Done) + assert.True(t, durationEqual(3*expected, end.Round(time.Second)), "expected %s to equal %s", expected, end) + }) +} diff --git a/printer/printer.go b/printer/printer.go index a7b6dcf5..9740524f 100644 --- a/printer/printer.go +++ b/printer/printer.go @@ -144,8 +144,29 @@ func (rp *ReportPrinter) getInfluxTags(addErrors bool) string { s = append(s, fmt.Sprintf(`call="%v"`, options.Call)) s = append(s, fmt.Sprintf(`host="%v"`, options.Host)) s = append(s, fmt.Sprintf("n=%v", options.Total)) - s = append(s, fmt.Sprintf("c=%v", options.Concurrency)) - s = append(s, fmt.Sprintf("qps=%v", options.QPS)) + + if options.CSchedule == "const" { + s = append(s, fmt.Sprintf("c=%v", options.Concurrency)) + } else { + s = append(s, fmt.Sprintf("concurrency-schedule=%v", options.CSchedule)) + s = append(s, fmt.Sprintf("concurrency-start=%v", options.CStart)) + s = append(s, fmt.Sprintf("concurrency-end=%v", options.CEnd)) + s = append(s, fmt.Sprintf("concurrency-step=%v", options.CStep)) + s = append(s, fmt.Sprintf("concurrency-step-duration=%v", options.CStepDuration)) + s = append(s, fmt.Sprintf("concurrency-max-duration=%v", options.CMaxDuration)) + } + + if options.LoadSchedule == "const" { + s = append(s, fmt.Sprintf("rps=%v", options.RPS)) + } else { + s = append(s, fmt.Sprintf("load-schedule=%v", options.LoadSchedule)) + s = append(s, fmt.Sprintf("load-start=%v", options.LoadStart)) + s = append(s, fmt.Sprintf("load-end=%v", options.LoadEnd)) + s = append(s, fmt.Sprintf("load-step=%v", options.LoadStep)) + s = append(s, fmt.Sprintf("load-step-duration=%v", options.LoadStepDuration)) + s = append(s, fmt.Sprintf("load-max-duration=%v", options.LoadMaxDuration)) + } + s = append(s, fmt.Sprintf("z=%v", options.Duration.Nanoseconds())) s = append(s, fmt.Sprintf("timeout=%v", options.Timeout.Seconds())) s = append(s, fmt.Sprintf("dial_timeout=%v", options.DialTimeout.Seconds())) @@ -374,424 +395,3 @@ func cleanInfluxString(input string) string { input = strings.Replace(input, "=", "\\=", -1) return input } - -var ( - defaultTmpl = ` -Summary: -{{ if .Name }} Name: {{ .Name }} -{{ end }} Count: {{ .Count }} - Total: {{ formatNanoUnit .Total }} - Slowest: {{ formatNanoUnit .Slowest }} - Fastest: {{ formatNanoUnit .Fastest }} - Average: {{ formatNanoUnit .Average }} - Requests/sec: {{ formatSeconds .Rps }} - -Response time histogram: -{{ histogram .Histogram }} -Latency distribution:{{ range .LatencyDistribution }} - {{ .Percentage }} % in {{ formatNanoUnit .Latency }} {{ end }} - -{{ if gt (len .StatusCodeDist) 0 }}Status code distribution: -{{ formatStatusCode .StatusCodeDist }}{{ end }} -{{ if gt (len .ErrorDist) 0 }}Error distribution: -{{ formatErrorDist .ErrorDist }}{{ end }} -` - - csvTmpl = ` -duration (ms),status,error{{ range $i, $v := .Details }} -{{ formatMilli .Latency.Seconds }},{{ .Status }},{{ .Error }}{{ end }} -` - - htmlTmpl = ` - - - - - - ghz{{ if .Name }} - {{ .Name }}{{end}} - - - - - - - - - - - -
- -
- {{ if .Name }} -

{{ .Name }}

- {{ end }} - {{ if .Date }} -

{{ formatDate .Date }}

- {{ end }} -
-
- -
- -
-
- - {{ if gt (len .Tags) 0 }} - -
-
- - {{ range $tag, $val := .Tags }} - -
-
- {{ $tag }} - {{ $val }} -
-
- - {{ end }} - -
-
-
- {{ end }} - -
-
-
-
- -

Summary

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Count{{ .Count }}
Total{{ formatNanoUnit .Total }}
Slowest{{ formatNanoUnit .Slowest }}
Fastest{{ formatNanoUnit .Fastest }}
Average{{ formatNanoUnit .Average }}
Requests / sec{{ formatSeconds .Rps }}
-
-
-
-
- - Options - -
-
-
{{ jsonify .Options true }}
-
-
-
-
-
-
- -
-
-
- -

Histogram

-
-

-

-

-
-
- -
-
-
- -

Latency distribution

-
- - - - {{ range .LatencyDistribution }} - - {{ end }} - - - - - {{ range .LatencyDistribution }} - - {{ end }} - - -
{{ .Percentage }} %
{{ formatNanoUnit .Latency }}
-
-
- -
-
-
-
-
- -

Status distribution

-
- - - - - - - - - - {{ range $code, $num := .StatusCodeDist }} - - - - - - {{ end }} - -
StatusCount% of Total
{{ $code }}{{ $num }}{{ formatPercent $num $.Count }} %
-
-
-
-
- - {{ if gt (len .ErrorDist) 0 }} - -
-
-
-
-
- -

Errors

-
- - - - - - - - - - {{ range $err, $num := .ErrorDist }} - - - - - - {{ end }} - -
ErrorCount% of Total
{{ $err }}{{ $num }}{{ formatPercent $num $.Count }} %
-
-
-
-
- - {{ end }} - -
-
-
-
-
- -

Data

-
- - JSON - CSV -
-
-
-
- -
-
-
-

- Generated by ghz -

- -
-
- -
- - - - - - - - -` -) diff --git a/printer/printer_test.go b/printer/printer_test.go index a8fdb828..8f23b6a9 100644 --- a/printer/printer_test.go +++ b/printer/printer_test.go @@ -19,11 +19,6 @@ func TestPrinter_getInfluxLine(t *testing.T) { report runner.Report expected string }{ - { - "empty", - runner.Report{}, - `ghz_run,call="",host="",n=0,c=0,qps=0,z=0,timeout=0,dial_timeout=0,keepalive=0,data="null",metadata="",tags="",errors=0,has_errors=false count=0,total=0,average=0,fastest=0,slowest=0,rps=0.00,errors=0 0`, - }, { "basic", runner.Report{ @@ -44,11 +39,13 @@ func TestPrinter_getInfluxLine(t *testing.T) { "Internal": 3, "DeadlineExceeded": 2}, Options: runner.Options{ - Call: "helloworld.Greeter.SayHello", - Proto: "/apis/greeter.proto", - Host: "0.0.0.0:50051", - Total: 200, - Concurrency: 50, + Call: "helloworld.Greeter.SayHello", + Proto: "/apis/greeter.proto", + Host: "0.0.0.0:50051", + LoadSchedule: "const", + CSchedule: "const", + Total: 200, + Concurrency: 50, Data: map[string]interface{}{ "name": "Bob Smith", }, @@ -116,7 +113,7 @@ func TestPrinter_getInfluxLine(t *testing.T) { }, }, }, - fmt.Sprintf(`ghz_run,name="run\ name",proto="/apis/greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,qps=0,z=0,timeout=0,dial_timeout=0,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="{\"foo\ bar\":\"biz\ baz\"}",tags="",errors=5,has_errors=true count=200,total=2000000000,average=10000000,fastest=1000000,slowest=100000000,rps=2000.00,median=5000000,p95=20000000,errors=5 %+v`, unixTimeNow), + fmt.Sprintf(`ghz_run,name="run\ name",proto="/apis/greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,rps=0,z=0,timeout=0,dial_timeout=0,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="{\"foo\ bar\":\"biz\ baz\"}",tags="",errors=5,has_errors=true count=200,total=2000000000,average=10000000,fastest=1000000,slowest=100000000,rps=2000.00,median=5000000,p95=20000000,errors=5 %+v`, unixTimeNow), }, } @@ -138,11 +135,6 @@ func TestPrinter_printInfluxDetails(t *testing.T) { report runner.Report expected string }{ - { - "empty", - runner.Report{}, - ``, - }, { "basic", runner.Report{ @@ -163,11 +155,13 @@ func TestPrinter_printInfluxDetails(t *testing.T) { "Internal": 3, "DeadlineExceeded": 2}, Options: runner.Options{ - Call: "helloworld.Greeter.SayHello", - Proto: "/apis/greeter.proto", - Host: "0.0.0.0:50051", - Total: 200, - Concurrency: 50, + Call: "helloworld.Greeter.SayHello", + Proto: "/apis/greeter.proto", + Host: "0.0.0.0:50051", + Total: 200, + Concurrency: 50, + LoadSchedule: "const", + CSchedule: "const", Data: map[string]interface{}{ "name": "Bob Smith", }, @@ -235,7 +229,7 @@ func TestPrinter_printInfluxDetails(t *testing.T) { }, }, }, - fmt.Sprintf(`ghz_detail,name="run\ name",proto="/apis/greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,qps=0,z=0,timeout=0,dial_timeout=0,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="{\"foo\ bar\":\"biz\ baz\"}",tags="",hasError=false latency=1000000,error="",status="OK" %+v + fmt.Sprintf(`ghz_detail,name="run\ name",proto="/apis/greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,rps=0,z=0,timeout=0,dial_timeout=0,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="{\"foo\ bar\":\"biz\ baz\"}",tags="",hasError=false latency=1000000,error="",status="OK" %+v `, unixTimeNow), }, } diff --git a/printer/template.go b/printer/template.go new file mode 100644 index 00000000..e59f1c95 --- /dev/null +++ b/printer/template.go @@ -0,0 +1,437 @@ +package printer + +var ( + defaultTmpl = ` +Summary: +{{ if .Name }} Name: {{ .Name }} +{{ end }} Count: {{ .Count }} + Total: {{ formatNanoUnit .Total }} + Slowest: {{ formatNanoUnit .Slowest }} + Fastest: {{ formatNanoUnit .Fastest }} + Average: {{ formatNanoUnit .Average }} + Requests/sec: {{ formatSeconds .Rps }} + +Response time histogram: +{{ histogram .Histogram }} +Latency distribution:{{ range .LatencyDistribution }} + {{ .Percentage }} % in {{ formatNanoUnit .Latency }} {{ end }} + +{{ if gt (len .StatusCodeDist) 0 }}Status code distribution: +{{ formatStatusCode .StatusCodeDist }}{{ end }} +{{ if gt (len .ErrorDist) 0 }}Error distribution: +{{ formatErrorDist .ErrorDist }}{{ end }} +` + + csvTmpl = ` +duration (ms),status,error{{ range $i, $v := .Details }} +{{ formatMilli .Latency.Seconds }},{{ .Status }},{{ .Error }}{{ end }} +` + + htmlTmpl = ` + + + + + + ghz{{ if .Name }} - {{ .Name }}{{end}} + + + + + + + + + + + +
+ +
+ {{ if .Name }} +

{{ .Name }}

+ {{ end }} + {{ if .Date }} +

{{ formatDate .Date }}

+ {{ end }} +
+
+ +
+ +
+
+ + {{ if gt (len .Tags) 0 }} + +
+
+ + {{ range $tag, $val := .Tags }} + +
+
+ {{ $tag }} + {{ $val }} +
+
+ + {{ end }} + +
+
+
+ {{ end }} + +
+
+
+
+ +

Summary

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Count{{ .Count }}
Total{{ formatNanoUnit .Total }}
Slowest{{ formatNanoUnit .Slowest }}
Fastest{{ formatNanoUnit .Fastest }}
Average{{ formatNanoUnit .Average }}
Requests / sec{{ formatSeconds .Rps }}
+
+
+
+
+
+
+
+
+ +
+
+
+ +

Histogram

+
+

+

+

+
+
+ +
+
+
+ +

Latency distribution

+
+ + + + {{ range .LatencyDistribution }} + + {{ end }} + + + + + {{ range .LatencyDistribution }} + + {{ end }} + + +
{{ .Percentage }} %
{{ formatNanoUnit .Latency }}
+
+
+ +
+
+
+
+
+ +

Status distribution

+
+ + + + + + + + + + {{ range $code, $num := .StatusCodeDist }} + + + + + + {{ end }} + +
StatusCount% of Total
{{ $code }}{{ $num }}{{ formatPercent $num $.Count }} %
+
+
+
+
+ + {{ if gt (len .ErrorDist) 0 }} + +
+
+
+
+
+ +

Errors

+
+ + + + + + + + + + {{ range $err, $num := .ErrorDist }} + + + + + + {{ end }} + +
ErrorCount% of Total
{{ $err }}{{ $num }}{{ formatPercent $num $.Count }} %
+
+
+
+
+ + {{ end }} + +
+
+
+
+
+ +

Data

+
+ + JSON + CSV +
+
+
+
+ +
+ +
+
+ +

Options

+
+
+
+
{{ jsonify .Options true }}
+
+
+
+
+ +
+
+
+

+ Generated by ghz +

+ +
+
+ +
+ + + + + + + + +` +) diff --git a/runner/config.go b/runner/config.go index 8de2f981..dec3c21b 100644 --- a/runner/config.go +++ b/runner/config.go @@ -34,7 +34,6 @@ func (d Duration) String() string { // UnmarshalJSON is our custom unmarshaller with JSON support func (d *Duration) UnmarshalJSON(text []byte) error { - // strValue := string(text) first := text[0] last := text[len(text)-1] if first == '"' && last == '"' { @@ -68,9 +67,16 @@ type Config struct { Authority string `json:"authority" toml:"authority" yaml:"authority"` Insecure bool `json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty"` N uint `json:"total" toml:"total" yaml:"total" default:"200"` + Async bool `json:"async,omitempty" toml:"async,omitempty" yaml:"async,omitempty"` C uint `json:"concurrency" toml:"concurrency" yaml:"concurrency" default:"50"` + CSchedule string `json:"concurrency-schedule" toml:"concurrency-schedule" yaml:"concurrency-schedule" default:"const"` + CStart uint `json:"concurrency-start" toml:"concurrency-start" yaml:"concurrency-start" default:"1"` + CEnd uint `json:"concurrency-end" toml:"concurrency-end" yaml:"concurrency-end" default:"0"` + CStep int `json:"concurrency-step" toml:"concurrency-step" yaml:"concurrency-step" default:"0"` + CStepDuration Duration `json:"concurrency-step-duration" toml:"concurrency-step-duration" yaml:"concurrency-step-duration" default:"0"` + CMaxDuration Duration `json:"concurrency-max-duration" toml:"concurrency-max-duration" yaml:"concurrency-max-duration" default:"0"` Connections uint `json:"connections" toml:"connections" yaml:"connections" default:"1"` - QPS uint `json:"qps" toml:"qps" yaml:"qps"` + RPS uint `json:"rps" toml:"rps" yaml:"rps"` Z Duration `json:"duration" toml:"duration" yaml:"duration"` ZStop string `json:"duration-stop" toml:"duration-stop" yaml:"duration-stop" default:"close"` X Duration `json:"max-duration" toml:"max-duration" yaml:"max-duration"` @@ -94,6 +100,12 @@ type Config struct { Debug string `json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty"` Host string `json:"host" toml:"host" yaml:"host"` EnableCompression bool `json:"enable-compression,omitempty" toml:"enable-compression,omitempty" yaml:"enable-compression,omitempty"` + LoadSchedule string `json:"load-schedule" toml:"load-schedule" yaml:"load-schedule" default:"const"` + LoadStart uint `json:"load-start" toml:"load-start" yaml:"load-start"` + LoadEnd uint `json:"load-end" toml:"load-end" yaml:"load-end"` + LoadStep int `json:"load-step" toml:"load-step" yaml:"load-step"` + LoadStepDuration Duration `json:"load-step-duration" toml:"load-step-duration" yaml:"load-step-duration"` + LoadMaxDuration Duration `json:"load-max-duration" toml:"load-max-duration" yaml:"load-max-duration"` } func checkData(data interface{}) error { diff --git a/runner/config_test.go b/runner/config_test.go index 6b24351f..e82eb57d 100644 --- a/runner/config_test.go +++ b/runner/config_test.go @@ -58,8 +58,11 @@ func TestConfig_Load(t *testing.T) { Data: map[string]interface{}{ "f_strings": []interface{}{"123", "456"}, }, - Format: "summary", - DialTimeout: Duration(10 * time.Second), + Format: "summary", + DialTimeout: Duration(10 * time.Second), + LoadSchedule: "const", + CSchedule: "const", + CStart: 1, }, true, }, diff --git a/runner/counter.go b/runner/counter.go new file mode 100644 index 00000000..09b47fd6 --- /dev/null +++ b/runner/counter.go @@ -0,0 +1,23 @@ +package runner + +import "sync/atomic" + +// RequestCounter gets the request count +type RequestCounter interface { + Get() uint64 +} + +// Counter is an implementation of the request counter +type Counter struct { + c uint64 +} + +// Get retrieves the current count +func (c *Counter) Get() uint64 { + return atomic.LoadUint64(&c.c) +} + +// Inc increases the current count +func (c *Counter) Inc() uint64 { + return atomic.AddUint64(&c.c, 1) +} diff --git a/runner/options.go b/runner/options.go index 72afe9bb..2e4ebdc1 100644 --- a/runner/options.go +++ b/runner/options.go @@ -15,6 +15,7 @@ import ( "text/template" "time" + "github.com/bojand/ghz/load" "github.com/jhump/protoreflect/desc" "github.com/pkg/errors" "google.golang.org/grpc/credentials" @@ -25,6 +26,15 @@ import ( // CallData for the request is passed and can be used to access worker id, request number, etc... type BinaryDataFunc func(mtd *desc.MethodDescriptor, callData *CallData) []byte +// ScheduleConst is a constant load schedule +const ScheduleConst = "const" + +// ScheduleStep is the step load schedule +const ScheduleStep = "step" + +// ScheduleLine is the line load schedule +const ScheduleLine = "line" + // RunConfig represents the request Configs type RunConfig struct { // call settings @@ -45,10 +55,31 @@ type RunConfig struct { insecure bool authority string + // load + rps int + loadStart uint + loadEnd uint + loadStep int + loadSchedule string + loadDuration time.Duration + loadStepDuration time.Duration + + pacer load.Pacer + + // concurrency + c int + cStart uint + cEnd uint + cStep int + cSchedule string + cMaxDuration time.Duration + cStepDuration time.Duration + + workerTicker load.WorkerTicker + // test - n int - c int - qps int + n int + async bool // number of connections nConns int @@ -91,6 +122,126 @@ type RunConfig struct { // Option controls some aspect of run type Option func(*RunConfig) error +// NewConfig creates a new RunConfig from the options passed +func NewConfig(call, host string, options ...Option) (*RunConfig, error) { + + // init with defaults + c := &RunConfig{ + n: 200, + c: 50, + nConns: 1, + timeout: time.Duration(20 * time.Second), + dialTimeout: time.Duration(10 * time.Second), + cpus: runtime.GOMAXPROCS(-1), + zstop: "close", + loadSchedule: ScheduleConst, + } + + // apply options + for _, option := range options { + err := option(c) + + if err != nil { + return nil, err + } + } + + // host and call may have been applied via options + // only override if not present + if c.host == "" { + c.host = strings.TrimSpace(host) + } + + if c.call == "" { + c.call = strings.TrimSpace(call) + } + + // fix up durations + if c.z > 0 { + c.n = math.MaxInt32 + } + + // checks + if c.nConns > c.c { + return nil, errors.New("number of connections cannot be greater than concurrency") + } + + if c.call == "" { + return nil, errors.New("call required") + } + + if c.host == "" { + return nil, errors.New("host required") + } + + if c.loadSchedule != ScheduleConst && + c.loadSchedule != ScheduleStep && + c.loadSchedule != ScheduleLine { + return nil, fmt.Errorf(`schedule much be "%s", "%s", or "%s"`, + ScheduleConst, ScheduleStep, ScheduleLine) + } + + if c.loadSchedule == ScheduleStep || c.loadSchedule == ScheduleLine { + if c.loadStart == c.loadEnd { + return nil, errors.New("load start cannot equal load end") + } + + // step value for step schedule or + // slope for line schedule + if c.loadStep == 0 { + return nil, errors.New("invalid load step") + } + + if c.loadSchedule == ScheduleStep && c.loadStepDuration == 0 { + return nil, errors.New("invalid load step duration") + } + } + + if c.cSchedule == ScheduleStep || c.cSchedule == ScheduleLine { + if c.cStart == c.cEnd { + return nil, errors.New("concurrency start start cannot equal concurrency end") + } + + // step value for step schedule or + // slope for line schedule + if c.cStep == 0 { + return nil, errors.New("invalid concurrency step") + } + + if c.cSchedule == ScheduleStep && c.cStepDuration == 0 { + return nil, errors.New("invalid concurrency step duration") + } + } + + if c.loadSchedule == ScheduleLine { + c.loadStepDuration = time.Second + } + + if c.cSchedule == ScheduleLine { + c.cStepDuration = time.Second + } + + if c.skipFirst > 0 && int(c.skipFirst) > c.n { + return nil, errors.New("you cannot skip more requests than those run") + } + + creds, err := createClientTransportCredentials( + c.skipVerify, + c.cacert, + c.cert, + c.key, + c.cname, + ) + + if err != nil { + return nil, err + } + + c.creds = creds + + return c, nil +} + // WithConfigFromFile uses a configuration JSON file to populate the RunConfig // WithConfigFromFile("config.json") func WithConfigFromFile(file string) Option { @@ -233,11 +384,11 @@ func WithConcurrency(c uint) Option { } } -// WithQPS specifies the QPS (queries per second) limit option -// WithQPS(10) -func WithQPS(qps uint) Option { +// WithRPS specifies the RPS (requests per second) limit option +// WithRPS(10) +func WithRPS(v uint) Option { return func(o *RunConfig) error { - o.qps = int(qps) + o.rps = int(v) return nil } @@ -599,82 +750,160 @@ func WithTemplateFuncs(funcMap template.FuncMap) Option { } } -// NewConfig creates a new RunConfig from the options passed -func NewConfig(call, host string, options ...Option) (*RunConfig, error) { +// WithEnableCompression specifies that requests should be done using gzip Compressor +// WithEnableCompression(true) +func WithEnableCompression(enableCompression bool) Option { + return func(o *RunConfig) error { + o.enableCompression = enableCompression - // init with defaults - c := &RunConfig{ - n: 200, - c: 50, - nConns: 1, - timeout: time.Duration(20 * time.Second), - dialTimeout: time.Duration(10 * time.Second), - cpus: runtime.GOMAXPROCS(-1), - zstop: "close", + return nil } +} - // apply options - for _, option := range options { - err := option(c) - - if err != nil { - return nil, err +// WithLoadSchedule specifies the load schedule +// WithLoadSchedule("const") +func WithLoadSchedule(schedule string) Option { + return func(o *RunConfig) error { + s := strings.TrimSpace(schedule) + if len(s) > 0 { + o.loadSchedule = strings.ToLower(s) } + + return nil } +} - // host and call may have been applied via options - // only override if not present - if c.host == "" { - c.host = strings.TrimSpace(host) +// WithLoadStart specifies the load start +// WithLoadStart(5) +func WithLoadStart(start uint) Option { + return func(o *RunConfig) error { + o.loadStart = start + + return nil } +} - if c.call == "" { - c.call = strings.TrimSpace(call) +// WithLoadEnd specifies the load end +// WithLoadEnd(25) +func WithLoadEnd(end uint) Option { + return func(o *RunConfig) error { + o.loadEnd = end + + return nil } +} - // fix up durations - if c.z > 0 { - c.n = math.MaxInt32 +// WithLoadStep specifies the load step +// WithLoadStep(5) +func WithLoadStep(step int) Option { + return func(o *RunConfig) error { + o.loadStep = step + + return nil } +} - // checks - if c.nConns > c.c { - return nil, errors.New("Number of connections cannot be greater than concurrency") +// WithLoadStepDuration specifies the load step duration for step schedule +func WithLoadStepDuration(duration time.Duration) Option { + return func(o *RunConfig) error { + o.loadStepDuration = duration + + return nil } +} - if c.call == "" { - return nil, errors.New("Call required") +// WithLoadDuration specifies the load duration +func WithLoadDuration(duration time.Duration) Option { + return func(o *RunConfig) error { + o.loadDuration = duration + + return nil } +} - if c.host == "" { - return nil, errors.New("Host required") +// WithAsync specifies the async option +func WithAsync(async bool) Option { + return func(o *RunConfig) error { + o.async = async + + return nil } +} - if c.skipFirst > 0 && int(c.skipFirst) > c.n { - return nil, errors.New("You cannot skip more requests than those run") +// WithConcurrencySchedule specifies the concurrency adjustment schedule +// WithConcurrencySchedule("const") +func WithConcurrencySchedule(schedule string) Option { + return func(o *RunConfig) error { + s := strings.TrimSpace(schedule) + if len(s) > 0 { + o.cSchedule = strings.ToLower(s) + } + + return nil } - creds, err := createClientTransportCredentials( - c.skipVerify, - c.cacert, - c.cert, - c.key, - c.cname, - ) +} - if err != nil { - return nil, err +// WithConcurrencyStart specifies the concurrency start for line or step schedule +// WithConcurrencyStart(5) +func WithConcurrencyStart(v uint) Option { + return func(o *RunConfig) error { + o.cStart = v + + return nil } +} - c.creds = creds +// WithConcurrencyEnd specifies the concurrency end value for line or step schedule +// WithConcurrencyEnd(25) +func WithConcurrencyEnd(v uint) Option { + return func(o *RunConfig) error { + o.cEnd = v - return c, nil + return nil + } } -// WithEnableCompression specifies that requests should be done using gzip Compressor -// WithEnableCompression(true) -func WithEnableCompression(enableCompression bool) Option { +// WithConcurrencyStep specifies the concurrency step value or slope +// WithConcurrencyStep(5) +func WithConcurrencyStep(step int) Option { return func(o *RunConfig) error { - o.enableCompression = enableCompression + o.cStep = step + + return nil + } +} + +// WithConcurrencyStepDuration specifies the concurrency step duration for step schedule +func WithConcurrencyStepDuration(duration time.Duration) Option { + return func(o *RunConfig) error { + o.cStepDuration = duration + + return nil + } +} + +// WithConcurrencyDuration specifies the total concurrency adjustment duration +func WithConcurrencyDuration(duration time.Duration) Option { + return func(o *RunConfig) error { + o.cMaxDuration = duration + + return nil + } +} + +// WithPacer specified the custom pacer to use +func WithPacer(p load.Pacer) Option { + return func(o *RunConfig) error { + o.pacer = p + + return nil + } +} + +// WithWorkerTicker specified the custom worker ticker to use +func WithWorkerTicker(ticker load.WorkerTicker) Option { + return func(o *RunConfig) error { + o.workerTicker = ticker return nil } @@ -740,7 +969,7 @@ func fromConfig(cfg *Config) []Option { WithAuthority(cfg.Authority), WithConcurrency(cfg.C), WithTotalRequests(cfg.N), - WithQPS(cfg.QPS), + WithRPS(cfg.RPS), WithTimeout(time.Duration(cfg.Timeout)), WithRunDuration(time.Duration(cfg.Z)), WithDialTimeout(time.Duration(cfg.DialTimeout)), @@ -754,6 +983,19 @@ func fromConfig(cfg *Config) []Option { WithConnections(cfg.Connections), WithEnableCompression(cfg.EnableCompression), WithDurationStopAction(cfg.ZStop), + WithLoadSchedule(cfg.LoadSchedule), + WithLoadStart(cfg.LoadStart), + WithLoadStep(cfg.LoadStep), + WithLoadStepDuration(time.Duration(cfg.LoadStepDuration)), + WithLoadEnd(cfg.LoadEnd), + WithLoadDuration(time.Duration(cfg.LoadMaxDuration)), + WithAsync(cfg.Async), + WithConcurrencySchedule(cfg.CSchedule), + WithConcurrencyStart(cfg.CStart), + WithConcurrencyEnd(cfg.CEnd), + WithConcurrencyStep(cfg.CStep), + WithConcurrencyStepDuration(time.Duration(cfg.CStepDuration)), + WithConcurrencyDuration(time.Duration(cfg.CMaxDuration)), func(o *RunConfig) error { o.call = cfg.Call return nil @@ -784,5 +1026,6 @@ func fromConfig(cfg *Config) []Option { if len(cfg.BinDataPath) > 0 { options = append(options, WithBinaryDataFromFile(cfg.BinDataPath)) } + return options } diff --git a/runner/options_test.go b/runner/options_test.go index 89d26603..d8501b38 100644 --- a/runner/options_test.go +++ b/runner/options_test.go @@ -48,7 +48,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, false, c.insecure) assert.Equal(t, 200, c.n) assert.Equal(t, 50, c.c) - assert.Equal(t, 0, c.qps) + assert.Equal(t, 0, c.rps) assert.Equal(t, false, c.binary) assert.Equal(t, 0, c.skipFirst) assert.Equal(t, time.Duration(0), c.z) @@ -66,13 +66,22 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, c.enableCompression, false) }) + t.Run("skipFirst > n", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithSkipFirst(1000), + ) + + assert.Error(t, err) + }) + t.Run("with options", func(t *testing.T) { c, err := NewConfig( "call", "localhost:50050", WithInsecure(true), WithTotalRequests(100), WithConcurrency(20), - WithQPS(5), + WithRPS(5), WithSkipFirst(5), WithRunDuration(time.Duration(5*time.Minute)), WithKeepalive(time.Duration(60*time.Second)), @@ -92,7 +101,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, true, c.insecure) assert.Equal(t, math.MaxInt32, c.n) assert.Equal(t, 20, c.c) - assert.Equal(t, 5, c.qps) + assert.Equal(t, 5, c.rps) assert.Equal(t, 5, c.skipFirst) assert.Equal(t, false, c.binary) assert.Equal(t, time.Duration(5*time.Minute), c.z) @@ -118,7 +127,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { WithAuthority("someauth"), WithTotalRequests(100), WithConcurrency(20), - WithQPS(5), + WithRPS(5), WithSkipFirst(5), WithKeepalive(time.Duration(60*time.Second)), WithTimeout(time.Duration(10*time.Second)), @@ -143,7 +152,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, "someauth", c.authority) assert.Equal(t, 100, c.n) assert.Equal(t, 20, c.c) - assert.Equal(t, 5, c.qps) + assert.Equal(t, 5, c.rps) assert.Equal(t, 5, c.skipFirst) assert.Equal(t, true, c.binary) assert.Equal(t, time.Duration(0), c.z) @@ -193,7 +202,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { WithCertificate("../testdata/localhost.crt", "../testdata/localhost.key"), WithInsecure(true), WithConcurrency(20), - WithQPS(5), + WithRPS(5), WithSkipFirst(5), WithRunDuration(time.Duration(5*time.Minute)), WithKeepalive(time.Duration(60*time.Second)), @@ -216,7 +225,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, "../testdata/localhost.key", c.key) assert.Equal(t, math.MaxInt32, c.n) assert.Equal(t, 20, c.c) - assert.Equal(t, 5, c.qps) + assert.Equal(t, 5, c.rps) assert.Equal(t, 5, c.skipFirst) assert.Equal(t, false, c.binary) assert.Equal(t, time.Duration(5*time.Minute), c.z) @@ -249,7 +258,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, false, c.insecure) assert.Equal(t, 200, c.n) assert.Equal(t, 50, c.c) - assert.Equal(t, 0, c.qps) + assert.Equal(t, 0, c.rps) assert.Equal(t, 0, c.skipFirst) assert.Equal(t, time.Duration(0), c.z) assert.Equal(t, time.Duration(0), c.keepaliveTime) @@ -279,7 +288,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, false, c.insecure) assert.Equal(t, 200, c.n) assert.Equal(t, 50, c.c) - assert.Equal(t, 0, c.qps) + assert.Equal(t, 0, c.rps) assert.Equal(t, 0, c.skipFirst) assert.Equal(t, false, c.binary) assert.Equal(t, time.Duration(0), c.z) @@ -314,7 +323,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, false, c.insecure) assert.Equal(t, 200, c.n) assert.Equal(t, 50, c.c) - assert.Equal(t, 0, c.qps) + assert.Equal(t, 0, c.rps) assert.Equal(t, 0, c.skipFirst) assert.Equal(t, 1, c.nConns) assert.Equal(t, false, c.binary) @@ -351,7 +360,7 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, false, c.insecure) assert.Equal(t, 200, c.n) assert.Equal(t, 50, c.c) - assert.Equal(t, 0, c.qps) + assert.Equal(t, 0, c.rps) assert.Equal(t, 0, c.skipFirst) assert.Equal(t, 5, c.nConns) assert.Equal(t, false, c.binary) @@ -469,4 +478,153 @@ func TestRunConfig_newRunConfig(t *testing.T) { assert.Equal(t, true, c.insecure) }) }) + + t.Run("invalid schedule", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule("foo"), + ) + + assert.Error(t, err) + }) + + t.Run("with load step", func(t *testing.T) { + t.Run("no step", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule(ScheduleStep), + WithLoadStart(10), + WithLoadDuration(20*time.Second), + WithLoadEnd(20), + ) + + assert.Error(t, err) + }) + + t.Run("no step duration", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule(ScheduleStep), + WithLoadStep(5), + ) + + assert.Error(t, err) + }) + + t.Run("with all load settings", func(t *testing.T) { + c, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule(ScheduleStep), + WithLoadStep(5), + WithLoadStart(10), + WithLoadDuration(20*time.Second), + WithLoadStepDuration(5*time.Second), + WithLoadEnd(20), + ) + + assert.NoError(t, err) + + assert.Equal(t, ScheduleStep, c.loadSchedule) + assert.Equal(t, uint(10), c.loadStart) + assert.Equal(t, uint(20), c.loadEnd) + assert.Equal(t, 20*time.Second, c.loadDuration) + assert.Equal(t, 5, c.loadStep) + assert.Equal(t, 5*time.Second, c.loadStepDuration) + }) + }) + + t.Run("with load line", func(t *testing.T) { + t.Run("no step", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule(ScheduleLine), + ) + + assert.Error(t, err) + }) + + t.Run("with all setting", func(t *testing.T) { + c, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithLoadSchedule(ScheduleLine), + WithLoadDuration(20*time.Second), + WithLoadStepDuration(5*time.Second), // overwritten + WithLoadEnd(20), + WithLoadStep(2), + ) + + assert.NoError(t, err) + + assert.Equal(t, ScheduleLine, c.loadSchedule) + assert.Equal(t, uint(0), c.loadStart) + assert.Equal(t, uint(20), c.loadEnd) + assert.Equal(t, 20*time.Second, c.loadDuration) + assert.Equal(t, 2, c.loadStep) + assert.Equal(t, 1*time.Second, c.loadStepDuration) + }) + }) + + t.Run("with concurrency step", func(t *testing.T) { + t.Run("no step", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithConcurrencySchedule(ScheduleStep), + ) + + assert.Error(t, err) + }) + + t.Run("with all concurrency settings", func(t *testing.T) { + c, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithConcurrencySchedule(ScheduleStep), + WithConcurrencyStep(5), + WithConcurrencyStart(10), + WithConcurrencyDuration(20*time.Second), + WithConcurrencyStepDuration(5*time.Second), + WithConcurrencyEnd(20), + ) + + assert.NoError(t, err) + + assert.Equal(t, ScheduleStep, c.cSchedule) + assert.Equal(t, uint(10), c.cStart) + assert.Equal(t, uint(20), c.cEnd) + assert.Equal(t, 20*time.Second, c.cMaxDuration) + assert.Equal(t, 5, c.cStep) + assert.Equal(t, 5*time.Second, c.cStepDuration) + }) + }) + + t.Run("with concurrency line", func(t *testing.T) { + t.Run("no step", func(t *testing.T) { + _, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithConcurrencySchedule(ScheduleLine), + ) + + assert.Error(t, err) + }) + + t.Run("with all concurrency settings", func(t *testing.T) { + c, err := NewConfig(" call ", " localhost:50050 ", + WithProtoFile("testdata/data.proto", []string{}), + WithConcurrencySchedule(ScheduleLine), + WithConcurrencyStep(2), + WithConcurrencyStart(5), + WithConcurrencyDuration(20*time.Second), + WithConcurrencyStepDuration(5*time.Second), // overwritten + WithConcurrencyEnd(20), + ) + + assert.NoError(t, err) + + assert.Equal(t, ScheduleLine, c.cSchedule) + assert.Equal(t, uint(5), c.cStart) + assert.Equal(t, uint(20), c.cEnd) + assert.Equal(t, 20*time.Second, c.cMaxDuration) + assert.Equal(t, 2, c.cStep) + assert.Equal(t, 1*time.Second, c.cStepDuration) + }) + }) } diff --git a/runner/reporter.go b/runner/reporter.go index 473f9dd8..6687d78b 100644 --- a/runner/reporter.go +++ b/runner/reporter.go @@ -24,22 +24,40 @@ type Reporter struct { // Options represents the request options type Options struct { - Host string `json:"host,omitempty"` - Proto string `json:"proto,omitempty"` - Protoset string `json:"protoset,omitempty"` - ImportPaths []string `json:"import-paths,omitempty"` - Call string `json:"call,omitempty"` - CACert string `json:"cacert,omitempty"` - Cert string `json:"cert,omitempty"` - Key string `json:"key,omitempty"` - SkipTLS bool `json:"skipTLS,omitempty"` - SkipFirst uint `json:"skipFirst,omitempty"` - CName string `json:"cname,omitempty"` - Authority string `json:"authority,omitempty"` - Insecure bool `json:"insecure"` - Total uint `json:"total,omitempty"` + Call string `json:"call,omitempty"` + Host string `json:"host,omitempty"` + Proto string `json:"proto,omitempty"` + Protoset string `json:"protoset,omitempty"` + ImportPaths []string `json:"import-paths,omitempty"` + EnableCompression bool `json:"enable-compression,omitempty"` + + CACert string `json:"cacert,omitempty"` + Cert string `json:"cert,omitempty"` + Key string `json:"key,omitempty"` + CName string `json:"cname,omitempty"` + SkipTLS bool `json:"skipTLS,omitempty"` + Insecure bool `json:"insecure"` + Authority string `json:"authority,omitempty"` + + RPS uint `json:"rps,omitempty"` + LoadSchedule string `json:"load-schedule"` + LoadStart uint `json:"load-start"` + LoadEnd uint `json:"load-end"` + LoadStep int `json:"load-step"` + LoadStepDuration time.Duration `json:"load-step-duration"` + LoadMaxDuration time.Duration `json:"load-max-duration"` + Concurrency uint `json:"concurrency,omitempty"` - QPS uint `json:"qps,omitempty"` + CSchedule string `json:"concurrency-schedule"` + CStart uint `json:"concurrency-start"` + CEnd uint `json:"concurrency-end"` + CStep int `json:"concurrency-step"` + CStepDuration time.Duration `json:"concurrency-step-duration"` + CMaxDuration time.Duration `json:"concurrency-max-duration"` + + Total uint `json:"total,omitempty"` + Async bool `json:"async,omitempty"` + Connections uint `json:"connections,omitempty"` Duration time.Duration `json:"duration,omitempty"` Timeout time.Duration `json:"timeout,omitempty"` @@ -52,6 +70,8 @@ type Options struct { CPUs int `json:"CPUs"` Name string `json:"name,omitempty"` + + SkipFirst uint `json:"skipFirst,omitempty"` } // Report holds the data for the full test @@ -175,30 +195,50 @@ func (r *Reporter) Finalize(stopReason StopReason, total time.Duration) *Report StatusCodeDist: r.statusCodeDist} rep.Options = Options{ - Host: r.config.host, - Proto: r.config.proto, - Protoset: r.config.protoset, - ImportPaths: r.config.importPaths, - Call: r.config.call, - CACert: r.config.cacert, - Cert: r.config.cert, - Key: r.config.key, - CName: r.config.cname, - SkipTLS: r.config.skipVerify, - SkipFirst: uint(r.config.skipFirst), - Insecure: r.config.insecure, - Authority: r.config.authority, - Total: uint(r.config.n), + Call: r.config.call, + Host: r.config.host, + Proto: r.config.proto, + Protoset: r.config.protoset, + ImportPaths: r.config.importPaths, + EnableCompression: r.config.enableCompression, + + CACert: r.config.cacert, + Cert: r.config.cert, + Key: r.config.key, + CName: r.config.cname, + SkipTLS: r.config.skipVerify, + Insecure: r.config.insecure, + Authority: r.config.authority, + + RPS: uint(r.config.rps), + LoadSchedule: r.config.loadSchedule, + LoadStart: r.config.loadStart, + LoadEnd: r.config.loadEnd, + LoadStep: r.config.loadStep, + LoadStepDuration: r.config.loadStepDuration, + LoadMaxDuration: r.config.loadDuration, + Concurrency: uint(r.config.c), - QPS: uint(r.config.qps), + CSchedule: r.config.cSchedule, + CStart: r.config.cStart, + CEnd: r.config.cEnd, + CStep: r.config.cStep, + CStepDuration: r.config.cStepDuration, + CMaxDuration: r.config.cMaxDuration, + + Total: uint(r.config.n), + Async: r.config.async, + Connections: uint(r.config.nConns), Duration: r.config.z, Timeout: r.config.timeout, DialTimeout: r.config.dialTimeout, KeepaliveTime: r.config.keepaliveTime, - Binary: r.config.binary, - CPUs: r.config.cpus, - Name: r.config.name, + + Binary: r.config.binary, + CPUs: r.config.cpus, + Name: r.config.name, + SkipFirst: uint(r.config.skipFirst), } _ = json.Unmarshal(r.config.data, &rep.Options.Data) diff --git a/runner/reporter_test.go b/runner/reporter_test.go index b5963dfc..d0f9d596 100644 --- a/runner/reporter_test.go +++ b/runner/reporter_test.go @@ -25,7 +25,7 @@ func TestReport_MarshalJSON(t *testing.T) { json, err := json.Marshal(&r) assert.NoError(t, err) - expected := `{"date":"2006-01-02T15:04:00-07:00","options":{"insecure":false,"binary":false,"CPUs":0},"count":1000,"total":10000000000,"average":500000000,"fastest":10000000,"slowest":1000000000,"rps":34567.89,"errorDistribution":null,"statusCodeDistribution":null,"latencyDistribution":null,"histogram":null,"details":null}` + expected := `{"date":"2006-01-02T15:04:00-07:00","options":{"insecure":false,"load-schedule":"","load-start":0,"load-end":0,"load-step":0,"load-step-duration":0,"load-max-duration":0,"concurrency-schedule":"","concurrency-start":0,"concurrency-end":0,"concurrency-step":0,"concurrency-step-duration":0,"concurrency-max-duration":0,"binary":false,"CPUs":0},"count":1000,"total":10000000000,"average":500000000,"fastest":10000000,"slowest":1000000000,"rps":34567.89,"errorDistribution":null,"statusCodeDistribution":null,"latencyDistribution":null,"histogram":null,"details":null}` assert.Equal(t, expected, string(json)) } diff --git a/runner/requester.go b/runner/requester.go index 5231a9e0..20874d59 100644 --- a/runner/requester.go +++ b/runner/requester.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/bojand/ghz/load" "github.com/bojand/ghz/protodesc" "github.com/jhump/protoreflect/desc" "github.com/jhump/protoreflect/dynamic" @@ -50,14 +51,11 @@ type Requester struct { stopCh chan bool start time.Time - qpsTick time.Duration - - reqCounter int64 - arrayJSONData []string - stopReason StopReason lock sync.Mutex + stopReason StopReason + workers []*Worker } // NewRequester creates a new requestor from the passed RunConfig @@ -66,17 +64,12 @@ func NewRequester(c *RunConfig) (*Requester, error) { var err error var mtd *desc.MethodDescriptor - var qpsTick time.Duration - if c.qps > 0 { - qpsTick = time.Duration(1e6/(c.qps)) * time.Microsecond - } - reqr := &Requester{ config: c, - qpsTick: qpsTick, stopReason: ReasonNormalEnd, results: make(chan *callResult, min(c.c*1000, maxResult)), - stopCh: make(chan bool, c.c), + stopCh: make(chan bool, 1), + workers: make([]*Worker, 0, c.c), conns: make([]*grpc.ClientConn, 0, c.nConns), stubs: make([]grpcdynamic.Stub, 0, c.nConns), } @@ -156,13 +149,16 @@ func NewRequester(c *RunConfig) (*Requester, error) { // Run makes all the requests and returns a report of results // It blocks until all work is done. func (b *Requester) Run() (*Report, error) { - start := time.Now() + + defer close(b.stopCh) cc, err := b.openClientConns() if err != nil { return nil, err } + start := time.Now() + b.lock.Lock() b.start = start @@ -179,7 +175,11 @@ func (b *Requester) Run() (*Report, error) { b.reporter.Run() }() - err = b.runWorkers() + wt := createWorkerTicker(b.config) + + p := createPacer(b.config) + + err = b.runWorkers(wt, p) report := b.Finish() b.closeClientConns() @@ -189,18 +189,16 @@ func (b *Requester) Run() (*Report, error) { // Stop stops the test func (b *Requester) Stop(reason StopReason) { + + b.stopCh <- true + b.lock.Lock() b.stopReason = reason - b.lock.Unlock() if b.config.hasLog { b.config.log.Debugf("Stopping with reason: %+v", reason) } - - // Send stop signal so that workers can stop gracefully. - for i := 0; i < b.config.c; i++ { - b.stopCh <- true - } + b.lock.Unlock() if b.config.zstop == "close" { b.closeClientConns() @@ -228,7 +226,12 @@ func (b *Requester) Finish() *Report { b.config.log.Debug("Finilizing report") } - return b.reporter.Finalize(b.stopReason, total) + var r StopReason + b.lock.Lock() + r = b.stopReason + b.lock.Unlock() + + return b.reporter.Finalize(r, total) } func (b *Requester) openClientConns() ([]*grpc.ClientConn, error) { @@ -330,59 +333,141 @@ func (b *Requester) newClientConn(withStatsHandler bool) (*grpc.ClientConn, erro return grpc.DialContext(ctx, b.config.host, opts...) } -func (b *Requester) runWorkers() error { - nReqPerWorker := b.config.n / b.config.c +func (b *Requester) runWorkers(wt load.WorkerTicker, p load.Pacer) error { - if b.config.c == 0 { - return nil - } + wct := wt.Ticker() - errC := make(chan error, b.config.c) + var wm sync.Mutex - // Ignore the case where b.N % b.C != 0. + // worker control ticker goroutine + go func() { + wt.Run() + }() - n := 0 // connection counter - for i := 0; i < b.config.c; i++ { // concurrency counter + errC := make(chan error, b.config.c) + done := make(chan struct{}) + ticks := make(chan TickValue) + counter := Counter{} - wID := "g" + strconv.Itoa(i) + "c" + strconv.Itoa(n) + go func() { + n := 0 + wc := 0 + for tv := range wct { + if b.config.hasLog { + b.config.log.Debugw("Worker ticker.", "delta", tv.Delta) + } - if len(b.config.name) > 0 { - wID = b.config.name + ":" + wID + if tv.Delta > 0 { + for i := 0; i < tv.Delta; i++ { + wID := "g" + strconv.Itoa(wc) + "c" + strconv.Itoa(n) + + if len(b.config.name) > 0 { + wID = b.config.name + ":" + wID + } + + if b.config.hasLog { + b.config.log.Debugw("Creating worker with ID: "+wID, + "workerID", wID, "requests per worker") + } + + w := Worker{ + ticks: ticks, + active: true, + stub: b.stubs[n], + mtd: b.mtd, + config: b.config, + stopCh: make(chan bool), + workerID: wID, + arrayJSONData: b.arrayJSONData, + } + + wc++ // increment worker id + + n++ // increment connection counter + + // wrap around connections if needed + if n == b.config.nConns { + n = 0 + } + + wm.Lock() + b.workers = append(b.workers, &w) + wm.Unlock() + + go func() { + errC <- w.runWorker() + }() + } + } else if tv.Delta < 0 { + nd := -1 * tv.Delta + wm.Lock() + wdc := 0 + for _, wrk := range b.workers { + if wdc == nd { + break + } + + wrk := wrk + if wrk.active { + wrk.Stop() + wdc++ + } + } + wm.Unlock() + } } + }() - if b.config.hasLog { - b.config.log.Debugw("Creating worker with ID: "+wID, - "workerID", wID, "requests per worker", nReqPerWorker) - } + go func() { + defer close(ticks) + defer wt.Finish() - w := Worker{ - stub: b.stubs[n], - mtd: b.mtd, - config: b.config, - stopCh: b.stopCh, - qpsTick: b.qpsTick, - reqCounter: &b.reqCounter, - nReq: nReqPerWorker, - workerID: wID, - arrayJSONData: b.arrayJSONData, - } + defer func() { + wm.Lock() + nw := len(b.workers) + for i := 0; i < nw; i++ { + b.workers[i].Stop() + } + wm.Unlock() + }() - n++ // increment connection counter + began := time.Now() + + for { + wait, stop := p.Pace(time.Since(began), counter.Get()) + + if stop { + if b.config.hasLog { + b.config.log.Debugw("Received stop from pacer.") + } + done <- struct{}{} + return + } - // wrap around connections if needed - if n == b.config.nConns { - n = 0 + if wait > 0 { + time.Sleep(wait) + } + + select { + case ticks <- TickValue{instant: time.Now(), reqNumber: counter.Inc() - 1}: + continue + case <-b.stopCh: + if b.config.hasLog { + b.config.log.Debugw("Signal received from stop channel.", "count", counter.Get()) + } + done <- struct{}{} + return + } } + }() - go func() { - errC <- w.runWorker() - }() - } + <-done var err error - for i := 0; i < b.config.c; i++ { + for i := 0; i < len(b.workers); i++ { err = multierr.Append(err, <-errC) } + return err } @@ -392,3 +477,65 @@ func min(a, b int) int { } return b } + +func createWorkerTicker(config *RunConfig) load.WorkerTicker { + if config.workerTicker != nil { + return config.workerTicker + } + + var wt load.WorkerTicker + switch config.cSchedule { + case ScheduleLine: + wt = &load.LineWorkerTicker{ + C: make(chan load.TickValue), + Start: config.cStart, + Slope: config.cStep, + Stop: config.cEnd, + MaxDuration: config.cMaxDuration, + } + case ScheduleStep: + wt = &load.StepWorkerTicker{ + C: make(chan load.TickValue), + Start: config.cStart, + Step: config.cStep, + Stop: config.cEnd, + StepDuration: config.cStepDuration, + MaxDuration: config.cMaxDuration, + } + default: + wt = &load.ConstWorkerTicker{N: uint(config.c), C: make(chan load.TickValue)} + } + + return wt +} + +func createPacer(config *RunConfig) load.Pacer { + if config.pacer != nil { + return config.pacer + } + + var p load.Pacer + switch config.loadSchedule { + case ScheduleLine: + p = &load.LinearPacer{ + Start: load.ConstantPacer{Freq: uint64(config.loadStart), Max: uint64(config.n)}, + Slope: int64(config.loadStep), + Stop: load.ConstantPacer{Freq: uint64(config.loadEnd), Max: uint64(config.n)}, + LoadDuration: config.loadDuration, + Max: uint64(config.n), + } + case ScheduleStep: + p = &load.StepPacer{ + Start: load.ConstantPacer{Freq: uint64(config.loadStart), Max: uint64(config.n)}, + Step: int64(config.loadStep), + Stop: load.ConstantPacer{Freq: uint64(config.loadEnd), Max: uint64(config.n)}, + LoadDuration: config.loadDuration, + StepDuration: config.loadStepDuration, + Max: uint64(config.n), + } + default: + p = &load.ConstantPacer{Freq: uint64(config.rps), Max: uint64(config.n)} + } + + return p +} diff --git a/runner/run.go b/runner/run.go index 5c5eef98..92463d72 100644 --- a/runner/run.go +++ b/runner/run.go @@ -36,6 +36,7 @@ func Run(call, host string, options ...Option) (*Report, error) { cancel := make(chan os.Signal, 1) signal.Notify(cancel, os.Interrupt) + go func() { <-cancel reqr.Stop(ReasonCancel) diff --git a/runner/run_test.go b/runner/run_test.go index 0c38bac6..e376f716 100644 --- a/runner/run_test.go +++ b/runner/run_test.go @@ -3,7 +3,6 @@ package runner import ( "fmt" "strconv" - "sync" "testing" "time" @@ -254,61 +253,47 @@ func TestRunUnary(t *testing.T) { assert.Equal(t, 1, connCount) }) - t.Run("test QPS", func(t *testing.T) { - gs.ResetCounters() + t.Run("test RPS", func(t *testing.T) { - var wg sync.WaitGroup + gs.ResetCounters() - wg.Add(1) + data := make(map[string]interface{}) + data["name"] = "bob" - time.AfterFunc(time.Duration(1500*time.Millisecond), func() { - count := gs.GetCount(callType) - assert.Equal(t, 2, count) - }) + report, err := Run( + "helloworld.Greeter.SayHello", + internal.TestLocalhost, + WithProtoFile("../testdata/greeter.proto", []string{}), + WithTotalRequests(10), + WithConcurrency(2), + WithRPS(1), + WithTimeout(time.Duration(20*time.Second)), + WithDialTimeout(time.Duration(20*time.Second)), + WithData(data), + WithInsecure(true), + ) - go func() { - data := make(map[string]interface{}) - data["name"] = "bob" + assert.NoError(t, err) - report, err := Run( - "helloworld.Greeter.SayHello", - internal.TestLocalhost, - WithProtoFile("../testdata/greeter.proto", []string{}), - WithTotalRequests(10), - WithConcurrency(2), - WithQPS(1), - WithTimeout(time.Duration(20*time.Second)), - WithDialTimeout(time.Duration(20*time.Second)), - WithData(data), - WithInsecure(true), - ) + assert.NotNil(t, report) - assert.NoError(t, err) + assert.Equal(t, 10, int(report.Count)) + assert.NotZero(t, report.Average) + assert.NotZero(t, report.Fastest) + assert.NotZero(t, report.Slowest) + assert.NotZero(t, report.Rps) + assert.Empty(t, report.Name) + assert.NotEmpty(t, report.Date) + assert.NotEmpty(t, report.Options) + assert.NotEmpty(t, report.Details) + assert.Equal(t, true, report.Options.Insecure) + assert.NotEmpty(t, report.LatencyDistribution) + assert.Equal(t, ReasonNormalEnd, report.EndReason) + assert.Empty(t, report.ErrorDist) - assert.NotNil(t, report) - - assert.Equal(t, 10, int(report.Count)) - assert.NotZero(t, report.Average) - assert.NotZero(t, report.Fastest) - assert.NotZero(t, report.Slowest) - assert.NotZero(t, report.Rps) - assert.Empty(t, report.Name) - assert.NotEmpty(t, report.Date) - assert.NotEmpty(t, report.Options) - assert.NotEmpty(t, report.Details) - assert.Equal(t, true, report.Options.Insecure) - assert.NotEmpty(t, report.LatencyDistribution) - assert.Equal(t, ReasonNormalEnd, report.EndReason) - assert.Empty(t, report.ErrorDist) - - assert.NotEqual(t, report.Average, report.Slowest) - assert.NotEqual(t, report.Average, report.Fastest) - assert.NotEqual(t, report.Slowest, report.Fastest) - - wg.Done() - - }() - wg.Wait() + assert.NotEqual(t, report.Average, report.Slowest) + assert.NotEqual(t, report.Average, report.Fastest) + assert.NotEqual(t, report.Slowest, report.Fastest) }) t.Run("test binary", func(t *testing.T) { diff --git a/runner/worker.go b/runner/worker.go index b581c171..47d9aadd 100644 --- a/runner/worker.go +++ b/runner/worker.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "sync/atomic" "time" "github.com/gogo/protobuf/proto" @@ -12,22 +11,28 @@ import ( "github.com/jhump/protoreflect/dynamic" "github.com/jhump/protoreflect/dynamic/grpcdynamic" "go.uber.org/multierr" + "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/encoding/gzip" "google.golang.org/grpc/metadata" ) +// TickValue is the tick value +type TickValue struct { + instant time.Time + reqNumber uint64 +} + // Worker is used for doing a single stream of requests in parallel type Worker struct { stub grpcdynamic.Stub mtd *desc.MethodDescriptor - config *RunConfig - stopCh chan bool - qpsTick time.Duration - reqCounter *int64 - nReq int - workerID string + config *RunConfig + workerID string + active bool + stopCh chan bool + ticks <-chan TickValue // cached messages only for binary cachedMessages []*dynamic.Message @@ -37,33 +42,43 @@ type Worker struct { } func (w *Worker) runWorker() error { - var throttle <-chan time.Time - if w.config.qps > 0 { - throttle = time.Tick(w.qpsTick) - } - var err error - for i := 0; i < w.nReq; i++ { - // Check if application is stopped. Do not send into a closed channel. + g := new(errgroup.Group) + + for { select { case <-w.stopCh: - return nil - default: - if w.config.qps > 0 { - <-throttle + if w.config.async { + return g.Wait() } - rErr := w.makeRequest() + return err - err = multierr.Append(err, rErr) + case tv := <-w.ticks: + if w.config.async { + g.Go(func() error { + return w.makeRequest(tv) + }) + } else { + rErr := w.makeRequest(tv) + err = multierr.Append(err, rErr) + } } } - return err } -func (w *Worker) makeRequest() error { +// Stop stops the worker. It has to be started with Run() again. +func (w *Worker) Stop() { + if !w.active { + return + } - reqNum := atomic.AddInt64(w.reqCounter, 1) + w.active = false + w.stopCh <- true +} + +func (w *Worker) makeRequest(tv TickValue) error { + reqNum := int64(tv.reqNumber) ctd := newCallData(w.mtd, w.config.funcs, w.workerID, reqNum) @@ -72,7 +87,7 @@ func (w *Worker) makeRequest() error { // try the optimized path for JSON data for non client-streaming if !w.config.binary && !w.mtd.IsClientStreaming() && len(w.arrayJSONData) > 0 { - indx := int((reqNum - 1) % int64(len(w.arrayJSONData))) // we want to start from inputs[0] so dec reqNum + indx := int(reqNum % int64(len(w.arrayJSONData))) // we want to start from inputs[0] so dec reqNum if inputs, err = w.getMessages(ctd, []byte(w.arrayJSONData[indx])); err != nil { return err } @@ -130,38 +145,22 @@ func (w *Worker) makeRequest() error { "input", inputs, "metadata", reqMD) } + inputsLen := len(inputs) + if inputsLen == 0 { + return fmt.Errorf("no data provided for request") + } + inputIdx := int(reqNum % int64(inputsLen)) // we want to start from inputs[0] so dec reqNum + unaryInput := inputs[inputIdx] + // RPC errors are handled via stats handler if w.mtd.IsClientStreaming() && w.mtd.IsServerStreaming() { _ = w.makeBidiRequest(&ctx, inputs) } else if w.mtd.IsClientStreaming() { _ = w.makeClientStreamingRequest(&ctx, inputs) + } else if w.mtd.IsServerStreaming() { + _ = w.makeServerStreamingRequest(&ctx, unaryInput) } else { - - inputsLen := len(inputs) - if inputsLen == 0 { - return fmt.Errorf("no data provided for request") - } - inputIdx := int((reqNum - 1) % int64(inputsLen)) // we want to start from inputs[0] so dec reqNum - - if w.mtd.IsServerStreaming() { - _ = w.makeServerStreamingRequest(&ctx, inputs[inputIdx]) - } else { - var res proto.Message - var resErr error - var callOptions = []grpc.CallOption{} - if w.config.enableCompression { - callOptions = append(callOptions, grpc.UseCompressor(gzip.Name)) - } - - res, resErr = w.stub.InvokeRpc(ctx, w.mtd, inputs[inputIdx], callOptions...) - - if w.config.hasLog { - w.config.log.Debugw("Received response", "workerID", w.workerID, "call type", callType, - "call", w.mtd.GetFullyQualifiedName(), - "input", inputs, "metadata", reqMD, - "response", res, "error", resErr) - } - } + _ = w.makeUnaryRequest(&ctx, reqMD, unaryInput) } return err @@ -202,6 +201,26 @@ func (w *Worker) getMessages(ctd *CallData, inputData []byte) ([]*dynamic.Messag return inputs, nil } +func (w *Worker) makeUnaryRequest(ctx *context.Context, reqMD *metadata.MD, input *dynamic.Message) error { + var res proto.Message + var resErr error + var callOptions = []grpc.CallOption{} + if w.config.enableCompression { + callOptions = append(callOptions, grpc.UseCompressor(gzip.Name)) + } + + res, resErr = w.stub.InvokeRpc(*ctx, w.mtd, input, callOptions...) + + if w.config.hasLog { + w.config.log.Debugw("Received response", "workerID", w.workerID, "call type", "unary", + "call", w.mtd.GetFullyQualifiedName(), + "input", input, "metadata", reqMD, + "response", res, "error", resErr) + } + + return resErr +} + func (w *Worker) makeClientStreamingRequest(ctx *context.Context, input []*dynamic.Message) error { var str *grpcdynamic.ClientStream var err error diff --git a/testdata/config/config0.json b/testdata/config/config0.json index 2bace5ed..d139dec1 100644 --- a/testdata/config/config0.json +++ b/testdata/config/config0.json @@ -9,7 +9,7 @@ "keepalive": 0, "total": 1000, "concurrency": 1, - "qps": 0, + "rps": 0, "host": "127.0.0.1:9000", "data": "foo" } \ No newline at end of file diff --git a/testdata/config/config0.toml b/testdata/config/config0.toml index 98707df4..e53981ef 100644 --- a/testdata/config/config0.toml +++ b/testdata/config/config0.toml @@ -10,8 +10,8 @@ keepalive = 0 total = 1000 # Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50. concurrency = 1 -# Rate limit, in queries per second (QPS). Default is no rate limit. -qps = 0 +# Rate limit, in requests per second (RPS). Default is no rate limit. +RPS = 0 host = "127.0.0.1:9000" data=""" { diff --git a/testdata/config/config0.yaml b/testdata/config/config0.yaml index d1993c36..1aec94a0 100644 --- a/testdata/config/config0.yaml +++ b/testdata/config/config0.yaml @@ -7,6 +7,6 @@ connections: 3 keepalive: 0 total: 1000 concurrency: 1 -qps: 0 +rps: 0 host: 127.0.0.1:9000 data: foo diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 00000000..dbdfec6d --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,26 @@ +// +build tools + +// This file ensures tool dependencies are kept in sync. This is the +// recommended way of doing this according to +// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module +// To install the following tools at the version used by this repo run: +// $ make tools +// or +// $ go generate -tags tools tools/tools.go + +package tools + +// NOTE: This must not be indented, so to stop goimports from trying to be +// helpful, it's separated out from the import block below. Please try to keep +// them in the same order. +//go:generate go install github.com/mfridman/tparse +//go:generate go install golang.org/x/tools/cmd/goimports +//go:generate go install github.com/golangci/golangci-lint/cmd/golangci-lint + +import ( + _ "github.com/mfridman/tparse" + + _ "github.com/golangci/golangci-lint/cmd/golangci-lint" + + _ "golang.org/x/tools/cmd/goimports" +) diff --git a/www/docs/concurrency.md b/www/docs/concurrency.md new file mode 100644 index 00000000..5d68e3b9 --- /dev/null +++ b/www/docs/concurrency.md @@ -0,0 +1,149 @@ +--- +id: concurrenct +title: Concurrency Options +--- + +This is a walkthrough of different concurrency options available to control the number of concurrent workers that `ghz` utilizes to make requests to the server. All examples are done using a simple unary gRPC call. + +Many of these options are similar to the load control options, but independently control the concurrent workers utilized. + +## Step Up Concurrency + +``` +./dist/ghz --insecure --async --proto /Users/bdjurkovic/go/grpc-helloworld-oc/helloworld/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -n 10000 --rps 200 \ + --concurrency-schedule=step --concurrency-start=5 --concurrency-step=5 --concurrency-end=50 --concurrency-step-duration=5s \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 50.05 s + Slowest: 52.04 ms + Fastest: 50.19 ms + Average: 50.59 ms + Requests/sec: 199.79 + +Response time histogram: + 50.187 [1] | + 50.373 [1786] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.558 [3032] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.743 [2822] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.929 [1536] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.114 [562] |∎∎∎∎∎∎∎ + 51.299 [194] |∎∎∎ + 51.485 [42] |∎ + 51.670 [15] | + 51.855 [6] | + 52.041 [4] | + +Latency distribution: + 10 % in 50.33 ms + 25 % in 50.42 ms + 50 % in 50.57 ms + 75 % in 50.73 ms + 90 % in 50.89 ms + 95 % in 51.01 ms + 99 % in 51.24 ms + +Status code distribution: +``` + +This test performs a constant load at `200` RPS, starting with `5` workers, and increasing concurrency by `5` workers every `5s` until we have `50` workers. At that point all `50` workers will be used to sustain the constant `200` RPS until `10000` total request limit is reached. Worker count over time would look something like: + +![Step Up Concurrency Constant Load](images/step_up_c_const_rps_wc.svg) + +## Step Down Concurrency + +``` +./dist/ghz --insecure --async --proto /Users/bdjurkovic/go/grpc-helloworld-oc/helloworld/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -n 10000 --rps 200 \ + --concurrency-schedule=step --concurrency-start=50 --concurrency-step=-5 \ + --concurrency-step-duration=5s --concurrency-max-duration=30s \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 50.05 s + Slowest: 52.13 ms + Fastest: 50.15 ms + Average: 50.63 ms + Requests/sec: 199.79 + +Response time histogram: + 50.152 [1] | + 50.350 [1145] |∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.548 [2476] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.746 [3491] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 50.943 [2202] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.141 [490] |∎∎∎∎∎∎ + 51.339 [148] |∎∎ + 51.536 [30] | + 51.734 [10] | + 51.932 [4] | + 52.130 [3] | + +Latency distribution: + 10 % in 50.34 ms + 25 % in 50.47 ms + 50 % in 50.63 ms + 75 % in 50.77 ms + 90 % in 50.89 ms + 95 % in 50.99 ms + 99 % in 51.24 ms + +Status code distribution: + [OK] 10000 responses +``` + +This test performs a constant load at `200` RPS, starting with `50` workers, and decreasing concurrency by `5` workers every `5s` until `30s` has elapsed. At that point all remaining workers will be used to sustain the constant `200` RPS until `10000` total request limit is reached. Worker count over time would look something like: + +![Step Down Concurrency Constant Load](images/step_down_c_const_rps_wc.svg) + +## Linear increase of concurrency + +``` +./dist/ghz --insecure --async --proto /Users/bdjurkovic/go/grpc-helloworld-oc/helloworld/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -n 10000 --rps 200 \ + --concurrency-schedule=line --concurrency-start=20 --concurrency-step=2 --concurrency-max-duration=30s \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 50.05 s + Slowest: 58.54 ms + Fastest: 50.16 ms + Average: 50.60 ms + Requests/sec: 199.79 + +Response time histogram: + 50.157 [1] | + 50.995 [9515] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.834 [477] |∎∎ + 52.672 [3] | + 53.510 [0] | + 54.349 [0] | + 55.187 [1] | + 56.025 [0] | + 56.864 [0] | + 57.702 [1] | + 58.540 [2] | + +Latency distribution: + 10 % in 50.31 ms + 25 % in 50.40 ms + 50 % in 50.60 ms + 75 % in 50.75 ms + 90 % in 50.89 ms + 95 % in 50.99 ms + 99 % in 51.25 ms + +Status code distribution: + [OK] 10000 responses +``` + +This test performs a constant load at `200` RPS, starting with `20` workers, and increasing concurrency linearly every `1s` by `2` workers until `30s` has elapsed. At that point all remaining workers will be used to sustain the constant `200` RPS until `10000` total request limit is reached. + +![Lene Up Concurrency Constant Load](images/line_up_c_const_rps_wc.svg) diff --git a/www/docs/images/const_c_const_rps.svg b/www/docs/images/const_c_const_rps.svg new file mode 100644 index 00000000..4f7aa082 --- /dev/null +++ b/www/docs/images/const_c_const_rps.svg @@ -0,0 +1,213 @@ +\n0s2s4s6s7s9s11s13s15s17s19s21s22s24s26s28s30s32s34s35s37s39s41s43s45s47s49s50s52s54s56sTime0.0020.0039.0058.0077.0096.00115.00134.00153.00172.00191.00210.00RPSconst_c_const_rps_aggr \ No newline at end of file diff --git a/www/docs/images/const_c_line_down_rps.svg b/www/docs/images/const_c_line_down_rps.svg new file mode 100644 index 00000000..2d6f97eb --- /dev/null +++ b/www/docs/images/const_c_line_down_rps.svg @@ -0,0 +1,263 @@ +\n0s3s5s8s11s14s16s19s22s24s27s30s32s35s38s40s43s46s48s51s54s56s59s1m2s1m4s1m7s1m10s1m13s1m15s1m18s1m20sTime0.0020.0039.0058.0077.0096.00115.00134.00153.00172.00191.00210.00RPSconst_c_line_down_rps_aggr \ No newline at end of file diff --git a/www/docs/images/const_c_line_up_rps.svg b/www/docs/images/const_c_line_up_rps.svg new file mode 100644 index 00000000..1f3533f4 --- /dev/null +++ b/www/docs/images/const_c_line_up_rps.svg @@ -0,0 +1,239 @@ +\n0s2s5s7s9s11s14s16s18s21s23s25s27s30s32s34s37s39s41s43s46s48s50s53s55s57s59s1m2s1m4s1m6s1m9sTime0.0030.0059.0088.00117.00146.00175.00204.00233.00262.00291.00320.00RPSconst_c_line_up_rps_aggr \ No newline at end of file diff --git a/www/docs/images/const_c_step_down_rps.svg b/www/docs/images/const_c_step_down_rps.svg new file mode 100644 index 00000000..e5912a78 --- /dev/null +++ b/www/docs/images/const_c_step_down_rps.svg @@ -0,0 +1,251 @@ +\n0s2s5s7s10s12s15s17s20s22s25s27s30s32s35s37s40s42s45s47s50s52s55s57s59s1m2s1m4s1m7s1m9s1m12s1m14sTime0.0020.0039.0058.0077.0096.00115.00134.00153.00172.00191.00210.00RPSconst_c_step_down_rps_aggr \ No newline at end of file diff --git a/www/docs/images/const_c_step_up_rps.svg b/www/docs/images/const_c_step_up_rps.svg new file mode 100644 index 00000000..92bd676b --- /dev/null +++ b/www/docs/images/const_c_step_up_rps.svg @@ -0,0 +1,283 @@ +\n0s3s6s9s12s15s18s21s24s27s30s33s36s39s42s45s49s52s55s58s1m1s1m4s1m7s1m10s1m13s1m16s1m19s1m22s1m25s1m28s1m31sTime0.0014.0028.0041.0055.0069.0082.0096.00110.00123.00137.00150.00RPSconst_c_step_up_rps_aggr \ No newline at end of file diff --git a/www/docs/images/line_up_c_const_rps_wc.svg b/www/docs/images/line_up_c_const_rps_wc.svg new file mode 100644 index 00000000..188fb4ce --- /dev/null +++ b/www/docs/images/line_up_c_const_rps_wc.svg @@ -0,0 +1,97 @@ +\n10-25 11:38:27AM10-25 11:38:35AM10-25 11:38:44AM10-25 11:38:53AM10-25 11:39:01AM10-25 11:39:10AM10-25 11:39:19AMTime0.007.1014.2021.3028.4035.5042.6049.7056.8063.9071.0078.00line_up_c_const_rps \ No newline at end of file diff --git a/www/docs/images/step_down_c_const_rps_wc.svg b/www/docs/images/step_down_c_const_rps_wc.svg new file mode 100644 index 00000000..970575c6 --- /dev/null +++ b/www/docs/images/step_down_c_const_rps_wc.svg @@ -0,0 +1,97 @@ +\n10-25 11:28:43AM10-25 11:28:52AM10-25 11:29:00AM10-25 11:29:09AM10-25 11:29:18AM10-25 11:29:26AM10-25 11:29:35AMTime0.004.609.1013.7018.2022.8027.3031.9036.4041.0045.5050.00step_down_c_const_rps \ No newline at end of file diff --git a/www/docs/images/step_up_c_const_rps_wc.svg b/www/docs/images/step_up_c_const_rps_wc.svg new file mode 100644 index 00000000..2bd00fc0 --- /dev/null +++ b/www/docs/images/step_up_c_const_rps_wc.svg @@ -0,0 +1,97 @@ +\n10-25 11:23:34AM10-25 11:23:43AM10-25 11:23:52AM10-25 11:24:01AM10-25 11:24:09AM10-25 11:24:18AM10-25 11:24:26AMTime0.004.609.1013.7018.2022.8027.3031.9036.4041.0045.5050.00step_up_c_const_rps \ No newline at end of file diff --git a/www/docs/load.md b/www/docs/load.md new file mode 100644 index 00000000..2acb9b07 --- /dev/null +++ b/www/docs/load.md @@ -0,0 +1,241 @@ +--- +id: load +title: Load Options +--- + +This is a walkthrough of different load options available to control the rate in requests per second (RPS) that `ghz` attempts to make to the server. All examples are done using a simple unary gRPC call. + +## Constant RPS + +``` +ghz --insecure --async \ + --proto /protos/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -c 10 -n 10000 --rps 200 \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 50.05 s + Slowest: 56.17 ms + Fastest: 50.17 ms + Average: 50.58 ms + Requests/sec: 199.79 + +Response time histogram: + 50.167 [1] | + 50.768 [8056] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.368 [1900] |∎∎∎∎∎∎∎∎∎ + 51.968 [37] | + 52.568 [1] | + 53.169 [0] | + 53.769 [0] | + 54.369 [1] | + 54.969 [1] | + 55.569 [0] | + 56.170 [3] | + +Latency distribution: + 10 % in 50.32 ms + 25 % in 50.40 ms + 50 % in 50.56 ms + 75 % in 50.72 ms + 90 % in 50.88 ms + 95 % in 51.00 ms + 99 % in 51.23 ms + +Status code distribution: + [OK] 10000 responses +``` + +This will perform a constant load RPS against the server. Graphed, it may look like this: + +![Constant Load](images/const_c_const_rps.svg) + +## Step Up RPS + +``` +ghz --insecure --async --proto /protos/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -c 10 -n 10000 \ + --load-schedule=step --load-start=50 --load-end=150 --load-step=10 --load-step-duration=5s \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 85.05 s + Slowest: 60.16 ms + Fastest: 50.18 ms + Average: 51.10 ms + Requests/sec: 117.57 + +Response time histogram: + 50.181 [1] | + 51.179 [5713] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 52.177 [3923] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 53.174 [311] |∎∎ + 54.172 [46] | + 55.170 [0] | + 56.168 [1] | + 57.166 [1] | + 58.164 [2] | + 59.161 [0] | + 60.159 [2] | + +Latency distribution: + 10 % in 50.48 ms + 25 % in 50.68 ms + 50 % in 51.07 ms + 75 % in 51.41 ms + 90 % in 51.68 ms + 95 % in 52.02 ms + 99 % in 52.77 ms + +Status code distribution: + [OK] 10000 responses +``` + +Performs step load starting at `50` RPS and inscreasing by `10` RPS every `5s` until we reach `150` RPS at which point the load is sustained at constant RPS rate until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +![Step Up Load](images/const_c_step_up_rps.svg) + +## Step Down RPS + +``` +ghz --insecure --async --proto /protos/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -c 10 -n 10000 \ + --load-schedule=step --load-start=200 --load-step=-10 --load-step-duration=5s --load-max-duration=40s \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 68.38 s + Slowest: 55.88 ms + Fastest: 50.16 ms + Average: 50.85 ms + Requests/sec: 146.23 + +Response time histogram: + 50.159 [1] | + 50.730 [4367] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.302 [4281] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.874 [1304] |∎∎∎∎∎∎∎∎∎∎∎∎ + 52.446 [43] | + 53.018 [0] | + 53.590 [0] | + 54.162 [2] | + 54.734 [0] | + 55.306 [0] | + 55.877 [2] | + +Latency distribution: + 10 % in 50.45 ms + 25 % in 50.60 ms + 50 % in 50.77 ms + 75 % in 51.06 ms + 90 % in 51.38 ms + 95 % in 51.54 ms + 99 % in 51.79 ms + +Status code distribution: + [OK] 10000 responses +``` + +Performs step load starting at `200` RPS and decreasing by `10` RPS every `10s` until `40s` has elapsed at which point the load is sustained at that RPS rate until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +![Step Down Load](images/const_c_step_down_rps.svg) + +## Linear load + +``` +ghz --insecure --async --proto /protos/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -c 10 -n 10000 \ + --load-schedule=line --load-start=5 --load-step=5 \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 62.80 s + Slowest: 56.61 ms + Fastest: 50.19 ms + Average: 50.72 ms + Requests/sec: 159.24 + +Response time histogram: + 50.189 [1] | + 50.832 [7552] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 51.474 [1781] |∎∎∎∎∎∎∎∎∎ + 52.117 [405] |∎∎ + 52.759 [139] |∎ + 53.402 [42] | + 54.044 [28] | + 54.687 [28] | + 55.329 [10] | + 55.972 [13] | + 56.614 [1] | + +Latency distribution: + 10 % in 50.34 ms + 25 % in 50.42 ms + 50 % in 50.57 ms + 75 % in 50.82 ms + 90 % in 51.22 ms + 95 % in 51.68 ms + 99 % in 53.16 ms + +Status code distribution: + [OK] 10000 responses +``` + +Performs linear load starting at `5` RPS and increasing by `5` RPS every `1s` until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +![Linear Up Load](images/const_c_line_up_rps.svg) + +## Linear load decrease + +``` +ghz --insecure --async --proto /protos/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -c 10 -n 10000 \ + --load-schedule=line --load-start=200 --load-step=-2 --load-end=100 \ + -d '{"name":"{{.WorkerID}}"}' 0.0.0.0:50051 + +Summary: + Count: 10000 + Total: 74.55 s + Slowest: 83.20 ms + Fastest: 50.18 ms + Average: 50.89 ms + Requests/sec: 134.13 + +Response time histogram: + 50.183 [1] | + 53.486 [9989] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 56.788 [5] | + 60.090 [1] | + 63.392 [3] | + 66.694 [0] | + 69.996 [0] | + 73.298 [0] | + 76.600 [0] | + 79.902 [0] | + 83.205 [1] | + +Latency distribution: + 10 % in 50.42 ms + 25 % in 50.57 ms + 50 % in 50.77 ms + 75 % in 51.11 ms + 90 % in 51.49 ms + 95 % in 51.73 ms + 99 % in 52.15 ms + +Status code distribution: + [OK] 10000 responses +``` + +Performs linear load starting at `200` RPS and decreasing by `2` RPS every `1s` until we reach `100` RPS at which point a constant rate is sustained until we accumulate `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +![Linear Down Load](images/const_c_line_down_rps.svg) diff --git a/www/docs/options.md b/www/docs/options.md index 31fb4eb4..76876cde 100644 --- a/www/docs/options.md +++ b/www/docs/options.md @@ -7,6 +7,16 @@ The `ghz` command line has numerous command line options. You can run `ghz --he +### `-config` + +Path to the JSON or TOML [config file](example_config.md) that specifies all the test settings. + +Config file settings can be combined with command line arguments. CLI options overwrite config file options. + +```sh +ghz --config=./config.json -c 20 -n 1000 +``` + ### `--proto` The path to The Protocol Buffer .proto file for input. If no `-proto` or `-protoset` options are used, we attempt to perform [server reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md). @@ -26,6 +36,10 @@ If no `-proto` or `-protoset` options are used, we attempt to perform server ref A fully-qualified method name in 'package.Service/Method' or 'package.Service.Method' format. For example: `helloworld.Greeter.SayHello`. With regard to measurement, we use [WithStatsHandler](https://godoc.org/google.golang.org/grpc#WithStatsHandler) option to capture call metrics. Specifically we only capture the [End](https://godoc.org/google.golang.org/grpc/stats#End) event which contains stats when an RPC ends. This should include the download of the payload and deserializing of the data. +### `-i`, `--import-paths` + +Comma separated list of proto import paths. The current working directory and the directory of the protocol buffer file specified using `-proto` are automatically added to the import list. + ### `--cacert` Path to the file containing trusted root certificates for verifying the server. By default `ghz` tries to create a secure connection using the system's default root certificate. The certificate file can be specified using `-cacert` option. The TLS verification can be skipped using `-skipTLS` option. @@ -54,27 +68,114 @@ Use plaintext and insecure connection. Value to be used as the `:authority` pseudo-header. Only works if `-insecure` is used. -### `-config` +### `--async` -Path to the JSON or TOML [config file](example_config.md) that specifies all the test settings. +Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one. -Config file settings can be combined with command line arguments. CLI options overwrite config file options. +### `-r`, `--rps` + +Rate limit in how many requsts per second (RPS) we perform in total. Default is no rate limit. The total RPS will be distributed among all the workers as specified by concurrency options. + +### `--load-schedule` + +Specifies the load schedule. Options are `const`, `step`, or `line`. Default is `const`. +With `const` load schedule we attempt to perform a constant RPS load as specified with the `q` option. +With `step` load schedule we do a step increase or decrease of RPS load as dictated by step load options: `load-start`, `load-step`, `load-end`, `load-step-duration`, and `load-max-duration`. +With `line` load schedule we do a linear increase or decrease of RPS load as dictated by step load options: `load-start`, `load-step`, `load-end`, and `load-max-duration`. Linear load is essentially step load with slop being specified using `load-step` option and `load-step-duration` is `1s`. + +Examples: ```sh -ghz --config=./config.json -c 20 -n 1000 +-n 10000 -c 10 --load-schedule=step --load-start=50 --load-step=10 --load-step-duration=5s +``` + +Performs step load starting at `50` RPS and inscreasing by `10` RPS every `5s` until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +```sh +-n 10000 -c 10 --load-schedule=step --load-start=50 --load-end=150 --load-step=10 --load-step-duration=5s ``` +Performs step load starting at `50` RPS and inscreasing by `10` RPS every `5s` until we reach `150` RPS at which point the load is sustained at constant RPS rate until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +```sh +-n 10000 -c 10 --load-schedule=step --load-start=50 --load-step=10 --load-step-duration=5s --load-max-duration=60s +``` + +Performs step load starting at `50` RPS and inscreasing by `10` RPS every `5s` until `60s` has elapsed at which point the load is sustained at that RPS rate until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +```sh +-n 10000 -c 10 --load-schedule=line --load-start=200 --load-step=-2 --load-end=50 +``` + +Performs linear load starting at `200` RPS and decreasing by `2` RPS every `1s` until `20` RPS has been reached, at which point the load is sustained at that RPS rate until we reach `10000` total requests. The RPS load is distributed among the `10` workers, all sharing `1` connection. + +### `--load-start` + +Specifies the starting RPS load value for step or line load schedules. + +### `--load-step` + +Specifies the load step value or slope value for step or line schedules. + +### `--load-end` + +Optional, specifies the load end value for step or line load schedules. Load adjustment is performed until either `load-end` rate is reached or `load-max-duration` duration has elapsed, which ever comes first. + +### `--load-max-duration` + +Optional, maximum duration to apply load adjustment. After this time has elapsed, constant load is performed at `load-end` setting value. Load adjustment is performed until either `load-end` rate is reached or `load-max-duration` duration has elapsed, which ever comes first. + ### `-c`, `--concurrency` -Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is `50`. For example to do requests in series without any concurrency set to `1`. `ghz` takes the `concurrency` argument and spawns that many worker goroutines. By default all goroutine workers share a single connection. +Number of workers to run concurrently when using `const` concurrency scheduler. -### `-n`, `--total` +### `--concurrency-schedule` -The total number of requests to run. Default is `200`. The combination of `-c` and `-n` are critical in how the benchmarking is done. `ghz` takes the `-c` argument and spawns that many worker goroutines. In parallel these goroutines each do their share (`n / c`) requests. So for example with the default `-c 50 -n 200` options we would spawn `50` goroutines which in parallel each do `4` requests. +Controls the concurrency (number of workers) adjustment, similarly how `load` settings control the RPS load adjustment. Options are `const`, `step`, or `line`. Default is `const`. + +Examples: -### `-q`, `--qps` +```sh +-n 100000 --rps 200 --concurrency-schedule=step --concurrency-start=5 --concurrency-step=5 --concurrency-end=50 --concurrency-step-duration=5s +``` -Rate limit for each worker goroutine in queries per second (QPS). Default is no rate limit. This essentially spaces out the requests by adding a timeout to each worker before it attempts each call. +Performs RPS load of `200` RPS. The number of concurrent workers starts at `5` and is increased by `5` every `5s` until we reach `50` workers. At that point we keep the sustained `200` RPS load spread over the `50` workers until total of `10000` requests is reached. That means as we increase the number of total concurrent workers, their share of RPS load decreases. + +```sh +-n 20000 -rps 200 --concurrency-schedule=step --concurrency-start=10 --concurrency-step=10 --concurrency-step-duration=5s --concurrency-max-duration=60s +``` + +Performs RPS load of `200` RPS. The number of concurrent workers starts at `10` and is increased by `10` every `5s` until `60s` has elapsed. At that point we keep the sustained `200` RPS load spread over the same number of workers until total of `20000` requests is reached. + +```sh +-n 10000 --rps 200 --concurrency-schedule=line --concurrency-start=200 --concurrency-step=-2 --concurrency-end=20 +``` + +Performs RPS load of `200` RPS. The number of concurrent workers starts at `200` and is decreased linearly by `2` every `1s` until we are at `20` concurrent workers. At that point we keep the sustained `200` RPS load spread over the same number of workers until total of `10000` requests is reached. As total number of active concurrent workers decreases, their share of RPS load increases. + +### `--concurrency-start` + +Concurrency start value for step and line concurrency schedules. + +### `--concurrency-end` + +Concurrency end value for step and line concurrency schedules. + +### `--concurrency-step=1` + +Concurrency step / slope value for step and line concurrency schedules. + +### `--concurrency-step-duration` + +Specifies the concurrency step duration value for step concurrency schedule. + +### `--concurrency-max-duration` + +Specifies the max concurrency adjustment duration value for step or line concurrency schedule. + +### `-n`, `--total` + +The total number of requests to run. Default is `200`. The combination of `-c` and `-n` are critical in how the benchmarking is done. `ghz` takes the `-c` argument and spawns that many worker goroutines. In parallel these goroutines each do their share (`n / c`) requests. So for example with the default `-c 50 -n 200` options we would spawn `50` goroutines which in parallel each do `4` requests. ### `-t`, `--timeout` @@ -84,17 +185,13 @@ Timeout for each request. Default is `20s`, use zero value for infinite. Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, `n` is ignored. Examples: `-z 10s` or `-z 3m`. -### `--duration-stop` - -Option on how to handle in-flight requests when duration specified using `duration` option is reached. Options are `close`, `wait`, and `ignore`. `close` will cause the connections to close immediately, and any requests that have yet to complete will likely error out and be reported with `transport is closing` error. `wait` will make all in-flight requests to be completed and reported. These requests still have the regular request `timeout` constraint. Finally, `ignore` option is similar to `close` that the connections are terminated immediately, however any in-flight requests that complete are completely ignored in the reporting. - ### `-x`, `--max-duration` Maximum duration of application to send requests with `n` setting respected. If duration is reached before `n` requests are completed, application stops and exits. Examples: `-x 10s` or `-x 3m`. -### `--connections` +### `--duration-stop` -By default we use a single gRPC connection for the whole test run, and the concurrency (`-c`) is achieved using goroutine workers sharing this single connection. The number of gRPC connections used can be controlled using this parameter. This parameter cannot exceed concurrency option. The specified number of connections will be distributed evenly to be shared among the concurrency goroutine workers. So for example a concurrency of `10` and using `5` connections will result in `10` goroutine workers, each pair of `2` workers sharing `1` of the `5` connections. Each worker will get its share of the total number of requests specified using `-n` option. +Option on how to handle in-flight requests when duration specified using `duration` option is reached. Options are `close`, `wait`, and `ignore`. `close` will cause the connections to close immediately, and any requests that have yet to complete will likely error out and be reported with `transport is closing` error. `wait` will make all in-flight requests to be completed and reported. These requests still have the regular request `timeout` constraint. Finally, `ignore` option is similar to `close` that the connections are terminated immediately, however any in-flight requests that complete are completely ignored in the reporting. ### `-d`, `--data` @@ -169,9 +266,15 @@ Output type. If none provided, a summary is printed. See [output formats page](output.md) for details. -### `-i`, `--import-paths` -Comma separated list of proto import paths. The current working directory and the directory of the protocol buffer file specified using `-proto` are automatically added to the import list. +### `--skipFirst` + +Skip the first `n` responses from the report. Helps remove initial warm-up requests from skewing the results. + + +### `--connections` + +By default we use a single gRPC connection for the whole test run, and the concurrency (`-c`) is achieved using goroutine workers sharing this single connection. The number of gRPC connections used can be controlled using this parameter. This parameter cannot exceed concurrency option. The specified number of connections will be distributed evenly to be shared among the concurrency goroutine workers. So for example a concurrency of `10` and using `5` connections will result in `10` goroutine workers, each pair of `2` workers sharing `1` of the `5` connections. Each worker will get its share of the total number of requests specified using `-n` option. ### `--connect-timeout` @@ -206,6 +309,10 @@ ghz --insecure \ 0.0.0.0:50051 ``` +### `-e`, `--enable-compression` + +Enable gzip compression on requests. + ### `-v`, `--version` Print the version. @@ -214,10 +321,3 @@ Print the version. Show context-sensitive help (also try --help-long and --help-man). -### `-e`, `--enable-compression` - -Enable gzip compression on requests. - -### `--skipFirst` - -Skip the first `n` responses from the report. Helps remove initial warm-up requests from skewing the results. diff --git a/www/docs/output.md b/www/docs/output.md index 5013ec96..80aaf38f 100644 --- a/www/docs/output.md +++ b/www/docs/output.md @@ -92,12 +92,12 @@ Using `-O json` outputs JSON data, and `-O pretty` outputs JSON in pretty format Using `-O influx-summary` outputs the summary data as [InfluxDB Line Protocol](https://docs.influxdata.com/influxdb/v1.6/concepts/glossary/#line-protocol). Sample output: ``` -ghz_run,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,qps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",errors=0,has_errors=false count=200,total=214737065,average=37806598,fastest=25759157,slowest=77504712,rps=931.37,median=36947515,p95=47421426,errors=0 1548107303068421000 +ghz_run,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,rps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",errors=0,has_errors=false count=200,total=214737065,average=37806598,fastest=25759157,slowest=77504712,rps=931.37,median=36947515,p95=47421426,errors=0 1548107303068421000 ``` Use `-O influx-details` to get the individual details for each request: ``` -ghz_detail,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,qps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",hasError=false latency=79044469,error="",status="OK" 1548107176979991000 -ghz_detail,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,qps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",hasError=false latency=43011582,error="",status="OK" 1548107177023123000 +ghz_detail,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,rps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",hasError=false latency=79044469,error="",status="OK" 1548107176979991000 +ghz_detail,name="Greeter\ SayHello",proto="./greeter.proto",call="helloworld.Greeter.SayHello",host="0.0.0.0:50051",n=200,c=50,rps=0,z=0,timeout=20,dial_timeout=10,keepalive=0,data="{\"name\":\"Bob\ Smith\"}",metadata="",tags="{\"created\ by\":\"Joe\ Developer\"\,\"env\":\"staging\"}",hasError=false latency=43011582,error="",status="OK" 1548107177023123000 ``` diff --git a/www/docs/usage.md b/www/docs/usage.md index a5f6cd35..a8af9be3 100644 --- a/www/docs/usage.md +++ b/www/docs/usage.md @@ -20,13 +20,29 @@ Flags: --skipTLS Skip TLS client verification of the server's certificate chain and host name. --insecure Use plaintext and insecure connection. --authority= Value to be used as the :authority pseudo-header. Only works if -insecure is used. - -c, --concurrency=50 Number of requests to run concurrently. Total number of requests cannot be smaller than the concurrency level. Default is 50. + --async Make requests asynchronous as soon as possible. Does not wait for request to finish before sending next one. + -r, --rps=0 Requests per second (RPS) rate limit for constant load schedule. Default is no rate limit. + --load-schedule="const" Specifies the load schedule. Options are const, step, or line. Default is const. + --load-start=0 Specifies the RPS load start value for step or line schedules. + --load-step=0 Specifies the load step value or slope value. + --load-end=0 Specifies the load end value for step or line load schedules. + --load-step-duration=0 Specifies the load step duration value for step load schedule. + --load-max-duration=0 Specifies the max load duration value for step or line load schedule. + -c, --concurrency=50 Number of request workers to run concurrently for const concurrency schedule. Default is 50. + --concurrency-schedule="const" + Concurrency change schedule. Options are const, step, or line. Default is const. + --concurrency-start=0 Concurrency start value for step and line concurrency schedules. + --concurrency-end=0 Concurrency end value for step and line concurrency schedules. + --concurrency-step=1 Concurrency step / slope value for step and line concurrency schedules. + --concurrency-step-duration=0 + Specifies the concurrency step duration value for step concurrency schedule. + --concurrency-max-duration=0 + Specifies the max concurrency adjustment duration value for step or line concurrency schedule. -n, --total=200 Number of requests to run. Default is 200. - -q, --qps=0 Rate limit, in queries per second (QPS). Default is no rate limit. -t, --timeout=20s Timeout for each request. Default is 20s, use 0 for infinite. -z, --duration=0 Duration of application to send requests. When duration is reached, application stops and exits. If duration is specified, n is ignored. Examples: -z 10s -z 3m. -x, --max-duration=0 Maximum duration of application to send requests with n setting respected. If duration is reached before n requests are completed, application stops and exits. Examples: -x 10s -x 3m. - --duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. + --duration-stop="close" Specifies how duration stop is reported. Options are close, wait or ignore. Default is close. -d, --data= The call data as stringified JSON. If the value is '@' then the request contents are read from stdin. -D, --data-file= File path for call data JSON file. Examples: /home/user/file.json or ./file.json. -b, --binary The call data comes as serialized binary message or multiple count-prefixed messages read from stdin. @@ -37,13 +53,13 @@ Flags: --reflect-metadata= Reflect metadata as stringified JSON used only for reflection request. -o, --output= Output path. If none provided stdout is used. -O, --format= Output format. One of: summary, csv, json, pretty, html, influx-summary, influx-details. Default is summary. + --skipFirst=0 Skip the first X requests when doing the results tally. --connections=1 Number of connections to use. Concurrency is distributed evenly among all the connections. Default is 1. --connect-timeout=10s Connection timeout for the initial connection dial. Default is 10s. --keepalive=0 Keepalive time duration. Only used if present and above 0. --name= User specified name for the test. --tags= JSON representation of user-defined string tags. --cpus=12 Number of cpu cores to use. - --skipFirst=0 Skip the first n responses from the report. Helps remove initial warm-up requests from skewing the results. --debug= The path to debug log file. -e, --enable-compression Enable Gzip compression on requests. -v, --version Show application version.