diff --git a/README.md b/README.md index ceaf4e1..c0f8499 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,93 @@ [![Godocs](https://img.shields.io/badge/golang-documentation-blue.svg)](https://godoc.org/github.com/AppsFlyer/wrk3) A golang generic benchmarking tool based mostly on Gil Tene's wrk2, only rewritten in go, and extended to allow running arbitrary protocols. + +## Usage +using the tool is done as follows: +1. create a go project +2. import the wrk3 lib +3. implement a function that creates the actual load e.g. call an http endpoint +4. create a main function that calls the wrk3.BenchmarkCmd() with the function mentioned above. +5. compile and run the benchmark + +let's look at an example of calling an http endpoint: +```go +package main + +import ( + "github.com/AppsFlyer/wrk3" + "io" + "io/ioutil" + "net/http" +) + +func sendHttpRequest(_ int) error { + resp, err := http.Get("http://localhost:8080/") + if resp != nil { + _, _ = io.Copy(ioutil.Discard, resp.Body) + _ = resp.Body.Close() + } + + return err +} + + +func main() { + wrk3.BenchmarkCmd(wrk3.RequestFunc(sendHttpRequest)) +} +``` + +the function that should be provided receives a local iteration index and should return an error if the call failed.\ +the errors are collected and included in the final report.\ +user supplied functions can also have state that can be used to create the request, to keep state an interface implementation can be supplied.\ +the interface looks as follows: +```go +type RequestHandler interface { + ExecuteRequest(localIndex int) error +} +``` +for more examples please refer to the examples folder in the codebase. + +## Running a benchmark +to run a benchmark simply compile the code and run it from the command line. you can pass it the following arguments: +1. concurrency - sets the level of concurrent requests generated by the tool +2. throughput - sets the target throughput the tool will try to reach. setting this target helps prevent coordinated omission which is the biggest advantage of wrk2 +3. duration - sets the amount of time the load test will run. any valid go duration units can be used. + +an example could be:\ +`./bench --concurrency=20 --throughput=10000 --duration=20s`\ + other flags could be added by using the default flag library and calling `wrk3.DefineBenchmarkFlags()` before calling `flag.Parse()` + +## Final report +at the end of the benchmark a report will be produced to the console, displaying the actual throughput as well as latency distribution.\ +an example report of the http benchmark could look as follows: +```text +running benchmark for 20s... +benchmark results: +total duration: 20.014761791s (target duration: 20s ) +total requests: 196089 +errors: 0 +omitted requests: 0 +throughput: 9797.218775202959 (target throughput: 10000 ) +latency distribution: +Quantile | Count | Value +------------+-----------+------------- +0.000 | 1 | 52.159µs +50.000 | 98271 | 78.911µs +75.000 | 147068 | 434.431µs +87.500 | 171578 | 14.565375ms +93.750 | 183849 | 39.124991ms +96.875 | 189964 | 77.594623ms +98.438 | 193026 | 123.600895ms +99.219 | 194559 | 157.941759ms +99.609 | 195326 | 192.020479ms +99.805 | 195708 | 226.099199ms +99.902 | 195898 | 238.551039ms +99.951 | 195996 | 244.449279ms +99.976 | 196043 | 246.022143ms +99.988 | 196067 | 246.939647ms +99.994 | 196079 | 247.726079ms +99.997 | 196085 | 247.988223ms +99.998 | 196089 | 248.119295ms +100.000 | 196089 | 248.119295ms +``` diff --git a/examples/grpc/grpc_example.go b/examples/grpc/grpc_example.go new file mode 100644 index 0000000..551b6d2 --- /dev/null +++ b/examples/grpc/grpc_example.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/AppsFlyer/wrk3" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/benchmark" + testpb "google.golang.org/grpc/benchmark/grpc_testing" +) + +type bench struct { + clients []testpb.BenchmarkServiceClient + req *testpb.SimpleRequest +} + +func (b *bench) ExecuteRequest(localIndex int) error { + client := b.clients[localIndex%len(b.clients)] + _, err := client.UnaryCall(context.Background(), b.req) + return err +} + +func main() { + var host = flag.String("host", "localhost", "host address/name to connect to.") + var port = flag.Int("port", 50051, "host port to connect to.") + + var numConn = flag.Int("c", 1, "The number of parallel connections.") + var rqSize = flag.Int("req", 1, "Request message size in bytes.") + var rspSize = flag.Int("resp", 1, "Response message size in bytes.") + + flag.Parse() + + request := &testpb.SimpleRequest{ + ResponseType: testpb.PayloadType_COMPRESSABLE, + ResponseSize: int32(*rspSize), + Payload: &testpb.Payload{ + Type: testpb.PayloadType_COMPRESSABLE, + Body: make([]byte, *rqSize), + }, + } + + b := &bench{ + clients: buildClients(*numConn, *host, *port), + req: request, + } + + wrk3.BenchmarkCmd(b) +} + +func buildClients(numConn int, host string, port int) []testpb.BenchmarkServiceClient { + ctx, connectCancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) + defer connectCancel() + + clients := make([]testpb.BenchmarkServiceClient, numConn) + addr := fmt.Sprintf("%v:%d", host, port) + for i := range clients { + conn := benchmark.NewClientConnWithContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock()) + clients[i] = testpb.NewBenchmarkServiceClient(conn) + } + return clients +} diff --git a/examples/http/http_example.go b/examples/http/http_example.go new file mode 100644 index 0000000..ebfe401 --- /dev/null +++ b/examples/http/http_example.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/AppsFlyer/wrk3" + "io" + "io/ioutil" + "net" + "net/http" + "time" +) + +var client = &http.Client{ + Timeout: 1 * time.Second, + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + MaxConnsPerHost: 100, + IdleConnTimeout: 90 * time.Second, + }, +} + +func sendHttpRequest(_ int) error { + resp, err := client.Get("http://localhost:8080/") + if resp != nil { + _, _ = io.Copy(ioutil.Discard, resp.Body) + _ = resp.Body.Close() + } + + return err +} + +func main() { + wrk3.BenchmarkCmd(wrk3.RequestFunc(sendHttpRequest)) +} diff --git a/go.mod b/go.mod index 4b718e5..e77a2dd 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd github.com/stretchr/testify v1.4.0 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + google.golang.org/grpc v1.29.1 ) diff --git a/go.sum b/go.sum index dd0f4b9..1967fa3 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,67 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/wrk3.go b/wrk3.go index 027cb7e..56775b9 100644 --- a/wrk3.go +++ b/wrk3.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "log" + "os" "sync" "time" @@ -12,13 +13,28 @@ import ( "golang.org/x/time/rate" ) -type RequestFunc func() error +// the interface to be implemented in order to supply specific load during the benchmark +// each time the method ExecuteRequest is called by the framework it should create a logical single unit of work(the request) +// the request can be a remote server call or any other type of load that needs to be benchmarked. +// the method should return an error if one was created during the execution of the request or nil otherwise +// the localIndex is an ever increasing index starting from zero, indicating the progress of a local batch of requests +type RequestHandler interface { + ExecuteRequest(localIndex int) error +} + +// alternatively, instead of providing an interface, one can provide a single function +// with the same signature as the one in the RequestHandler. the function will be lifted to an interface. +type RequestFunc func(int) error + +func (reqFunc RequestFunc) ExecuteRequest(localIndex int) error { + return reqFunc(localIndex) +} type Benchmark struct { Concurrency int Throughput float64 Duration time.Duration - SendRequest RequestFunc + SendRequest RequestHandler } type BenchResult struct { @@ -31,25 +47,27 @@ type BenchResult struct { } // BenchmarkCmd is a main function helper that runs the provided target function using the commandline arguments -func BenchmarkCmd(target RequestFunc) { - var concurrency int - var throughput float64 - var duration time.Duration +func BenchmarkCmd(target RequestHandler) { + var cmd = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + + var concurrency = cmd.Int("concurrency", 10, "level of benchmark concurrency") + var throughput = cmd.Float64("throughput", 10000, "target benchmark throughput") + var duration = cmd.Duration("duration", 20*time.Second, "benchmark time period") - flag.IntVar(&concurrency, "concurrency", 10, "level of benchmark concurrency") - flag.Float64Var(&throughput, "throughput", 10000, "target benchmark throughput") - flag.DurationVar(&duration, "duration", 20*time.Second, "benchmark time period") - flag.Parse() + err := cmd.Parse(os.Args[1:]) + if err != nil { + log.Fatal("can't parse command line flags", err) + } fmt.Printf("running benchmark for %v...\n", duration) b := Benchmark{ - Concurrency: concurrency, - Throughput: throughput, - Duration: duration, + Concurrency: *concurrency, + Throughput: *throughput, + Duration: *duration, SendRequest: target, } result := b.Run() - PrintBenchResult(throughput, duration, result) + PrintBenchResult(*throughput, *duration, result) } type localResult struct { @@ -76,13 +94,12 @@ type eventsGenerator struct { func (b Benchmark) Run() BenchResult { execution := b.newExecution() - execution.generateEvents(b.Throughput, 2*b.Concurrency) + go execution.generateEvents(b.Throughput, 2*b.Concurrency) for i := 0; i < b.Concurrency; i++ { go execution.sendRequests() } execution.awaitDone() - return execution.summarizeResults() } @@ -106,22 +123,20 @@ func newEventsGenerator(duration time.Duration, bufSize int) eventsGenerator { } func (e *eventsGenerator) generateEvents(throughput float64, burstSize int) { - go func() { - omitted := 0 - rateLimiter := rate.NewLimiter(rate.Limit(throughput), burstSize) - for err := rateLimiter.Wait(e.doneCtx); err == nil; err = rateLimiter.Wait(e.doneCtx) { - select { - case e.eventsBuf <- time.Now(): - default: - omitted++ - } + omitted := 0 + rateLimiter := rate.NewLimiter(rate.Limit(throughput), burstSize) + for err := rateLimiter.Wait(e.doneCtx); err == nil; err = rateLimiter.Wait(e.doneCtx) { + select { + case e.eventsBuf <- time.Now(): + default: + omitted++ } + } - close(e.eventsBuf) - e.lock.Lock() - e.omitted = omitted - e.lock.Unlock() - }() + close(e.eventsBuf) + e.lock.Lock() + e.omitted = omitted + e.lock.Unlock() } func (e *eventsGenerator) omittedCount() int { @@ -150,7 +165,7 @@ func (e *executioner) sendRequests() { case t, ok := <-e.eventsBuf: if ok { res.counter++ - err := e.benchmark.SendRequest() + err := e.benchmark.SendRequest.ExecuteRequest(res.counter) if err != nil { res.errors++ } diff --git a/wrk3_test.go b/wrk3_test.go index 4a64cad..3b9ad3e 100644 --- a/wrk3_test.go +++ b/wrk3_test.go @@ -44,7 +44,7 @@ func TestSlowHttpBench(t *testing.T) { Concurrency: 10, Throughput: 1000, Duration: expectedDuration, - SendRequest: createHTTPLoadFunction("http://localhost:8081/", 5*time.Second), + SendRequest: createHTTPLoadHandler("http://localhost:8081/", 5*time.Second), }.Run() assert.True(t, benchResult.Throughput < 500, "throughput too high for slow server. actual value %s", benchResult.Throughput) @@ -56,7 +56,34 @@ func TestSlowHttpBench(t *testing.T) { _ = server.Close() } -func createHTTPLoadFunction(url string, timeout time.Duration) func() error { +type handler struct { + client *http.Client + url string +} + +func (h *handler) ExecuteRequest(_ int) error { + resp, err := h.client.Get(h.url) + if resp != nil { + _, _ = io.Copy(ioutil.Discard, resp.Body) + _ = resp.Body.Close() + } + + return err +} + +func createHTTPLoadHandler(url string, timeout time.Duration) RequestHandler { + return &handler{ + url: url, + client: &http.Client{ + Transport: &http.Transport{ + MaxConnsPerHost: 200, + }, + Timeout: timeout, + }, + } +} + +func createHTTPLoadFunction(url string, timeout time.Duration) RequestFunc { client := &http.Client{ Transport: &http.Transport{ MaxConnsPerHost: 200, @@ -64,16 +91,13 @@ func createHTTPLoadFunction(url string, timeout time.Duration) func() error { Timeout: timeout, } - return func() error { + return func(index int) error { resp, err := client.Get(url) if resp != nil { _, _ = io.Copy(ioutil.Discard, resp.Body) _ = resp.Body.Close() } - if err != nil { - fmt.Println("error sending get request:", err) - } return err } } @@ -129,7 +153,7 @@ func TestEventsGenerator(t *testing.T) { start := time.Now() expectedDuration := 100 * time.Millisecond generator := newEventsGenerator(expectedDuration, int(throughput*2)) - generator.generateEvents(throughput, 10) + go generator.generateEvents(throughput, 10) generator.awaitDone() actualDuration := time.Since(start) @@ -139,7 +163,7 @@ func TestEventsGenerator(t *testing.T) { func TestSendRequestsWithErrors(t *testing.T) { expectedResults := 666 - e := configureExecutioner(expectedResults, func() error { return fmt.Errorf("baaahhh") }) + e := configureExecutioner(expectedResults, func(int) error { return fmt.Errorf("baaahhh") }) e.sendRequests() result := <-e.results @@ -149,7 +173,7 @@ func TestSendRequestsWithErrors(t *testing.T) { func TestSendRequests(t *testing.T) { expectedResults := 666 - e := configureExecutioner(expectedResults, func() error { return nil }) + e := configureExecutioner(expectedResults, func(int) error { return nil }) e.sendRequests() result := <-e.results @@ -160,7 +184,7 @@ func TestSendRequests(t *testing.T) { func TestSummarizeResults(t *testing.T) { expectedResults := 500 expectedLatency := time.Millisecond - e := configureExecutioner(expectedResults, func() error { + e := configureExecutioner(expectedResults, func(index int) error { time.Sleep(expectedLatency) return nil })