From 6485c21b79d8fb555ba1ed2f5ffe77c909df758d Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 14:56:37 -0500 Subject: [PATCH 01/16] track error counts rather than console output --- go-wrk.go | 4 ++++ loader/loader.go | 29 +++++++++++++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index 2622129..a7115a2 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -147,6 +147,9 @@ func main() { aggStats.MaxRequestTime = util.MaxDuration(aggStats.MaxRequestTime, stats.MaxRequestTime) aggStats.MinRequestTime = util.MinDuration(aggStats.MinRequestTime, stats.MinRequestTime) responders++ + for k,v := range stats.ErrMap { + aggStats.ErrMap[k] += v + } } } @@ -165,4 +168,5 @@ func main() { fmt.Printf("Fastest Request:\t%v\n", aggStats.MinRequestTime) fmt.Printf("Slowest Request:\t%v\n", aggStats.MaxRequestTime) fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) + fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) } diff --git a/loader/loader.go b/loader/loader.go index b33c22d..a532892 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -2,6 +2,7 @@ package loader import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -48,6 +49,7 @@ type RequesterStats struct { MaxRequestTime time.Duration NumRequests int NumErrs int + ErrMap map[error]int } func NewLoadCfg(duration int, // seconds @@ -103,7 +105,7 @@ func escapeUrlStr(in string) string { // DoRequest single request implementation. Returns the size of the response and its duration // On error - returns -1 on both -func DoRequest(httpClient *http.Client, header map[string]string, method, host, loadUrl, reqBody string) (respSize int, duration time.Duration) { +func DoRequest(httpClient *http.Client, header map[string]string, method, host, loadUrl, reqBody string) (respSize int, duration time.Duration, err error) { respSize = -1 duration = -1 @@ -116,8 +118,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, req, err := http.NewRequest(method, loadUrl, buf) if err != nil { - fmt.Println("An error occured doing request", err) - return + return 0,0,err } for hk, hv := range header { @@ -131,19 +132,16 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, start := time.Now() resp, err := httpClient.Do(req) if err != nil { - fmt.Println("redirect?") // this is a bit weird. When redirection is prevented, a url.Error is retuned. This creates an issue to distinguish // between an invalid URL that was provided and and redirection error. - rr, ok := err.(*url.Error) + _, ok := err.(*url.Error) if !ok { - fmt.Println("An error occured doing request", err, rr) - return + return 0,0,err } - fmt.Println("An error occured doing request", err) + return 0,0,err } if resp == nil { - fmt.Println("empty response") - return + return 0,0,errors.New("empty response") } defer func() { if resp != nil && resp.Body != nil { @@ -152,7 +150,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, }() body, err := ioutil.ReadAll(resp.Body) if err != nil { - fmt.Println("An error occured reading body", err) + return 0,0,err } if resp.StatusCode/100 == 2 { // Treat all 2XX as successful duration = time.Since(start) @@ -161,7 +159,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, duration = time.Since(start) respSize = int(resp.ContentLength) + int(util.EstimateHttpHeadersSize(resp.Header)) } else { - fmt.Println("received status code", resp.StatusCode, "from", resp.Header, "content", string(body), req) + return 0,0,errors.New(fmt.Sprint("received status code", resp.StatusCode, "from", resp.Header, "content", string(body), req)) } return @@ -180,8 +178,11 @@ func (cfg *LoadCfg) RunSingleLoadSession() { } for time.Since(start).Seconds() <= float64(cfg.duration) && atomic.LoadInt32(&cfg.interrupted) == 0 { - respSize, reqDur := DoRequest(httpClient, cfg.header, cfg.method, cfg.host, cfg.testUrl, cfg.reqBody) - if respSize > 0 { + respSize, reqDur, err := DoRequest(httpClient, cfg.header, cfg.method, cfg.host, cfg.testUrl, cfg.reqBody) + if err != nil { + stats.ErrMap[err]+=1 + stats.NumErrs++ + } else if respSize > 0 { stats.TotRespSize += int64(respSize) stats.TotDuration += reqDur stats.MaxRequestTime = util.MaxDuration(reqDur, stats.MaxRequestTime) From 85e9c134d2162ee49f3ef0a5b2662fbf5b0f78c9 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 14:56:51 -0500 Subject: [PATCH 02/16] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eda722f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.vscode/launch.json From a4db007983ccd35270b3946656ab8ee1da434ab9 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 15:01:36 -0500 Subject: [PATCH 03/16] initialize error map --- go-wrk.go | 2 +- loader/loader.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index a7115a2..a61bfb5 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -132,7 +132,7 @@ func main() { } responders := 0 - aggStats := loader.RequesterStats{MinRequestTime: time.Minute} + aggStats := loader.RequesterStats{MinRequestTime: time.Minute, ErrMap: make(map[error]int)} for responders < goroutines { select { diff --git a/loader/loader.go b/loader/loader.go index a532892..ba93d09 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -168,7 +168,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, // Requester a go function for repeatedly making requests and aggregating statistics as long as required // When it is done, it sends the results using the statsAggregator channel func (cfg *LoadCfg) RunSingleLoadSession() { - stats := &RequesterStats{MinRequestTime: time.Minute} + stats := &RequesterStats{MinRequestTime: time.Minute, ErrMap: make(map[error]int)} start := time.Now() httpClient, err := client(cfg.disableCompression, cfg.disableKeepAlive, cfg.skipVerify, From 11e07d1e69b7ed1649345cd41676a84da7d04f84 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 15:14:22 -0500 Subject: [PATCH 04/16] normalize error map to core error --- loader/loader.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/loader/loader.go b/loader/loader.go index ba93d09..a330375 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "net/http" "net/url" @@ -148,7 +147,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, resp.Body.Close() } }() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return 0,0,err } @@ -165,6 +164,13 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, return } +func unwrap(err error) error { + for errors.Unwrap(err)!=nil { + err = errors.Unwrap(err); + } + return err +} + // Requester a go function for repeatedly making requests and aggregating statistics as long as required // When it is done, it sends the results using the statsAggregator channel func (cfg *LoadCfg) RunSingleLoadSession() { @@ -180,7 +186,7 @@ func (cfg *LoadCfg) RunSingleLoadSession() { for time.Since(start).Seconds() <= float64(cfg.duration) && atomic.LoadInt32(&cfg.interrupted) == 0 { respSize, reqDur, err := DoRequest(httpClient, cfg.header, cfg.method, cfg.host, cfg.testUrl, cfg.reqBody) if err != nil { - stats.ErrMap[err]+=1 + stats.ErrMap[unwrap(err)]+=1 stats.NumErrs++ } else if respSize > 0 { stats.TotRespSize += int64(respSize) From dd9635106127b0a50753aada581356c7ee84a1e8 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 19:02:03 -0500 Subject: [PATCH 05/16] don't set GOMAXPROCS, let command line or default --- go-wrk.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index a61bfb5..facae70 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "os/signal" - "runtime" "strings" "time" @@ -70,9 +69,6 @@ func printDefaults() { } func main() { - //raising the limits. Some performance gains were achieved with the + goroutines (not a lot). - runtime.GOMAXPROCS(runtime.NumCPU() + goroutines) - statsAggregator = make(chan *loader.RequesterStats, goroutines) sigChan := make(chan os.Signal, 1) From 09ace47e4f168df048a3c69346bcc7ea6cace3ff Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 20:46:09 -0500 Subject: [PATCH 06/16] use hdr histogram for timings --- go-wrk.go | 23 ++++++++++++++++++----- go.mod | 5 ++++- go.sum | 47 +++++++++++++++++++++++++++++++++++++++++++++++ loader/loader.go | 9 ++++----- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index facae70..51fdf24 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -9,6 +9,7 @@ import ( "strings" "time" + histo "github.com/HdrHistogram/hdrhistogram-go" "github.com/tsliwowicz/go-wrk/loader" "github.com/tsliwowicz/go-wrk/util" ) @@ -128,7 +129,7 @@ func main() { } responders := 0 - aggStats := loader.RequesterStats{MinRequestTime: time.Minute, ErrMap: make(map[error]int)} + aggStats := loader.RequesterStats{ErrMap: make(map[error]int), Histogram: histo.New(1,int64(duration * 1000000),4)} for responders < goroutines { select { @@ -140,12 +141,11 @@ func main() { aggStats.NumRequests += stats.NumRequests aggStats.TotRespSize += stats.TotRespSize aggStats.TotDuration += stats.TotDuration - aggStats.MaxRequestTime = util.MaxDuration(aggStats.MaxRequestTime, stats.MaxRequestTime) - aggStats.MinRequestTime = util.MinDuration(aggStats.MinRequestTime, stats.MinRequestTime) responders++ for k,v := range stats.ErrMap { aggStats.ErrMap[k] += v } + aggStats.Histogram.Merge(stats.Histogram) } } @@ -161,8 +161,21 @@ func main() { bytesRate := float64(aggStats.TotRespSize) / avgThreadDur.Seconds() fmt.Printf("%v requests in %v, %v read\n", aggStats.NumRequests, avgThreadDur, util.ByteSize{float64(aggStats.TotRespSize)}) fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\nAvg Req Time:\t\t%v\n", reqRate, util.ByteSize{bytesRate}, avgReqTime) - fmt.Printf("Fastest Request:\t%v\n", aggStats.MinRequestTime) - fmt.Printf("Slowest Request:\t%v\n", aggStats.MaxRequestTime) + fmt.Printf("Fastest Request:\t%v\n", toDuration(aggStats.Histogram.Min())) + fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max())) fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) + fmt.Printf("10%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.10))) + fmt.Printf("50%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.50))) + fmt.Printf("75%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.75))) + fmt.Printf("99%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.99))) + fmt.Printf("99.9%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.999))) + fmt.Printf("99.9999%%:\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.999999))) + fmt.Printf("99.99999%%:\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.9999999))) + fmt.Printf("stddev:\t\t\t%v\n", toDuration(int64(aggStats.Histogram.StdDev()))) + // aggStats.Histogram.PercentilesPrint(os.Stdout,1,1) +} + +func toDuration(usecs int64) time.Duration { + return time.Duration(usecs*1000) } diff --git a/go.mod b/go.mod index 97f7c9a..65d401c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/tsliwowicz/go-wrk go 1.16 -require golang.org/x/net v0.7.0 +require ( + github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + golang.org/x/net v0.7.0 +) diff --git a/go.sum b/go.sum index 3dc4822..4392f1f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,39 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +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/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -10,6 +42,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -23,7 +57,20 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/loader/loader.go b/loader/loader.go index a330375..5fddf67 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -12,6 +12,7 @@ import ( "sync/atomic" "time" + histo "github.com/HdrHistogram/hdrhistogram-go" "github.com/tsliwowicz/go-wrk/util" ) @@ -44,11 +45,10 @@ type LoadCfg struct { type RequesterStats struct { TotRespSize int64 TotDuration time.Duration - MinRequestTime time.Duration - MaxRequestTime time.Duration NumRequests int NumErrs int ErrMap map[error]int + Histogram *histo.Histogram } func NewLoadCfg(duration int, // seconds @@ -174,7 +174,7 @@ func unwrap(err error) error { // Requester a go function for repeatedly making requests and aggregating statistics as long as required // When it is done, it sends the results using the statsAggregator channel func (cfg *LoadCfg) RunSingleLoadSession() { - stats := &RequesterStats{MinRequestTime: time.Minute, ErrMap: make(map[error]int)} + stats := &RequesterStats{ErrMap: make(map[error]int), Histogram: histo.New(1,int64(cfg.duration * 1000000),4)} start := time.Now() httpClient, err := client(cfg.disableCompression, cfg.disableKeepAlive, cfg.skipVerify, @@ -191,8 +191,7 @@ func (cfg *LoadCfg) RunSingleLoadSession() { } else if respSize > 0 { stats.TotRespSize += int64(respSize) stats.TotDuration += reqDur - stats.MaxRequestTime = util.MaxDuration(reqDur, stats.MaxRequestTime) - stats.MinRequestTime = util.MinDuration(reqDur, stats.MinRequestTime) + stats.Histogram.RecordValue(reqDur.Microseconds()); stats.NumRequests++ } else { stats.NumErrs++ From 741c9f965fca5c65eb323e8455d2396efcd68739 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 26 Jun 2024 20:54:36 -0500 Subject: [PATCH 07/16] use histogram for avg request time --- go-wrk.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index 51fdf24..bac7f3e 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -157,11 +157,11 @@ func main() { avgThreadDur := aggStats.TotDuration / time.Duration(responders) //need to average the aggregated duration reqRate := float64(aggStats.NumRequests) / avgThreadDur.Seconds() - avgReqTime := aggStats.TotDuration / time.Duration(aggStats.NumRequests) bytesRate := float64(aggStats.TotRespSize) / avgThreadDur.Seconds() fmt.Printf("%v requests in %v, %v read\n", aggStats.NumRequests, avgThreadDur, util.ByteSize{float64(aggStats.TotRespSize)}) - fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\nAvg Req Time:\t\t%v\n", reqRate, util.ByteSize{bytesRate}, avgReqTime) + fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", reqRate, util.ByteSize{bytesRate}) fmt.Printf("Fastest Request:\t%v\n", toDuration(aggStats.Histogram.Min())) + fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max())) fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) From 676f554cd08c1332a30e1ce1512685bd8b712aa6 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 10:43:02 -0500 Subject: [PATCH 08/16] allow setting GOMAXPROCS via command line --- go-wrk.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/go-wrk.go b/go-wrk.go index bac7f3e..37da830 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/signal" + "runtime" "strings" "time" @@ -38,6 +39,7 @@ var clientCert string var clientKey string var caCert string var http2 bool +var cpus int = 0 func init() { flag.BoolVar(&versionFlag, "v", false, "Print version details") @@ -49,6 +51,7 @@ func init() { flag.IntVar(&goroutines, "c", 10, "Number of goroutines to use (concurrent connections)") flag.IntVar(&duration, "d", 10, "Duration of test in seconds") flag.IntVar(&timeoutms, "T", 1000, "Socket/request timeout in ms") + flag.IntVar(&cpus, "cpus", 0, "Number of cpus, i.e. GOMAXPROCS. 0 = system default.") flag.StringVar(&method, "M", "GET", "HTTP method") flag.StringVar(&host, "host", "", "Host Header") flag.Var(&headerFlags, "H", "Header to add to each request (you can define multiple -H flags)") @@ -70,6 +73,7 @@ func printDefaults() { } func main() { + statsAggregator = make(chan *loader.RequesterStats, goroutines) sigChan := make(chan os.Signal, 1) @@ -109,6 +113,10 @@ func main() { return } + if cpus > 0 { + runtime.GOMAXPROCS(cpus) + } + fmt.Printf("Running %vs test @ %v\n %v goroutine(s) running concurrently\n", duration, testUrl, goroutines) if len(reqBody) > 0 && reqBody[0] == '@' { From 65ce00ac846f0d2e5a1b3be2fa065dc983ab3f20 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 10:45:58 -0500 Subject: [PATCH 09/16] update README for histogram --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5783768..d208571 100644 --- a/README.md +++ b/README.md @@ -41,21 +41,30 @@ Command line parameters (./go-wrk -help) Basic Usage ----------- - ./go-wrk -c 80 -d 5 http://192.168.1.118:8080/json + ./go-wrk -c 2048 -d 10 http://localhost:8080/plaintext -This runs a benchmark for 5 seconds, using 80 go routines (connections) +This runs a benchmark for 10 seconds, using 2048 go routines (connections) Output: - Running 10s test @ http://192.168.1.118:8080/json - 80 goroutine(s) running concurrently - 142470 requests in 4.949028953s, 19.57MB read - Requests/sec: 28787.47 - Transfer/sec: 3.95MB - Avg Req Time: 0.0347ms - Fastest Request: 0.0340ms - Slowest Request: 0.0421ms - Number of Errors: 0 + Running 10s test @ http://localhost:8080/plaintext + 2048 goroutine(s) running concurrently + 439977 requests in 10.012950719s, 52.45MB read + Requests/sec: 43940.79 + Transfer/sec: 5.24MB + Fastest Request: 98µs + Avg Req Time: 46.608ms + Slowest Request: 398.431ms + Number of Errors: 0 + Error Counts: map[] + 10%: 164µs + 50%: 2.382ms + 75%: 3.83ms + 99%: 5.403ms + 99.9%: 5.488ms + 99.9999%: 5.5ms + 99.99999%: 5.5ms + stddev: 29.744ms Benchmarking Tips From 929552ce6cfa9004be65983d2f1fe9cecfd723a1 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 10:58:46 -0500 Subject: [PATCH 10/16] update Go module versions --- .gitignore | 1 + go.mod | 4 ++-- go.sum | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index eda722f..dc9734d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store .vscode/launch.json +go-wrk diff --git a/go.mod b/go.mod index 65d401c..9181366 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/tsliwowicz/go-wrk go 1.16 require ( - github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect - golang.org/x/net v0.7.0 + github.com/HdrHistogram/hdrhistogram-go v1.1.2 + golang.org/x/net v0.26.0 ) diff --git a/go.sum b/go.sum index 4392f1f..7a90cad 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,7 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 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= @@ -22,6 +23,10 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -33,14 +38,29 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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= @@ -49,20 +69,40 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From f3f01ecf573eae75a4e16992db7eeb48e874ca74 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 11:25:32 -0500 Subject: [PATCH 11/16] track errors by string to coalesce --- go-wrk.go | 6 ++++-- loader/loader.go | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index 37da830..31b80bb 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -137,7 +137,7 @@ func main() { } responders := 0 - aggStats := loader.RequesterStats{ErrMap: make(map[error]int), Histogram: histo.New(1,int64(duration * 1000000),4)} + aggStats := loader.RequesterStats{ErrMap: make(map[string]int), Histogram: histo.New(1,int64(duration * 1000000),4)} for responders < goroutines { select { @@ -159,7 +159,9 @@ func main() { if aggStats.NumRequests == 0 { fmt.Println("Error: No statistics collected / no requests found\n") - return + fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) + fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) + return } avgThreadDur := aggStats.TotDuration / time.Duration(responders) //need to average the aggregated duration diff --git a/loader/loader.go b/loader/loader.go index 5fddf67..71f2f34 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -47,7 +47,7 @@ type RequesterStats struct { TotDuration time.Duration NumRequests int NumErrs int - ErrMap map[error]int + ErrMap map[string]int Histogram *histo.Histogram } @@ -158,7 +158,7 @@ func DoRequest(httpClient *http.Client, header map[string]string, method, host, duration = time.Since(start) respSize = int(resp.ContentLength) + int(util.EstimateHttpHeadersSize(resp.Header)) } else { - return 0,0,errors.New(fmt.Sprint("received status code", resp.StatusCode, "from", resp.Header, "content", string(body), req)) + return 0,0,errors.New(fmt.Sprint("received status code ", resp.StatusCode)) } return @@ -174,7 +174,7 @@ func unwrap(err error) error { // Requester a go function for repeatedly making requests and aggregating statistics as long as required // When it is done, it sends the results using the statsAggregator channel func (cfg *LoadCfg) RunSingleLoadSession() { - stats := &RequesterStats{ErrMap: make(map[error]int), Histogram: histo.New(1,int64(cfg.duration * 1000000),4)} + stats := &RequesterStats{ErrMap: make(map[string]int), Histogram: histo.New(1,int64(cfg.duration * 1000000),4)} start := time.Now() httpClient, err := client(cfg.disableCompression, cfg.disableKeepAlive, cfg.skipVerify, @@ -186,7 +186,7 @@ func (cfg *LoadCfg) RunSingleLoadSession() { for time.Since(start).Seconds() <= float64(cfg.duration) && atomic.LoadInt32(&cfg.interrupted) == 0 { respSize, reqDur, err := DoRequest(httpClient, cfg.header, cfg.method, cfg.host, cfg.testUrl, cfg.reqBody) if err != nil { - stats.ErrMap[unwrap(err)]+=1 + stats.ErrMap[unwrap(err).Error()]+=1 stats.NumErrs++ } else if respSize > 0 { stats.TotRespSize += int64(respSize) From 7d69529821b4a08631149a1d3b70510f8ecdaf93 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 11:41:52 -0500 Subject: [PATCH 12/16] better error map presentation --- go-wrk.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index 31b80bb..2604e70 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -72,6 +72,14 @@ func printDefaults() { }) } +func mapToString(m map[string]int) string { + s := make([]string,0,len(m)) + for k,v := range m { + s = append(s,fmt.Sprint(k,"=",v)) + } + return strings.Join(s,",") +} + func main() { statsAggregator = make(chan *loader.RequesterStats, goroutines) @@ -160,7 +168,7 @@ func main() { if aggStats.NumRequests == 0 { fmt.Println("Error: No statistics collected / no requests found\n") fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) - fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) + fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) return } @@ -174,7 +182,7 @@ func main() { fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max())) fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) - fmt.Printf("Error Counts:\t\t%v\n", aggStats.ErrMap) + fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) fmt.Printf("10%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.10))) fmt.Printf("50%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.50))) fmt.Printf("75%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.75))) From 72555dd85347c8a36917d63b815d8c8667fb3c04 Mon Sep 17 00:00:00 2001 From: robert engels Date: Thu, 27 Jun 2024 11:46:20 -0500 Subject: [PATCH 13/16] do not print error map if empty --- go-wrk.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index 2604e70..c4ed68e 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -166,10 +166,12 @@ func main() { } if aggStats.NumRequests == 0 { - fmt.Println("Error: No statistics collected / no requests found\n") + fmt.Println("Error: No statistics collected / no requests found") fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) - fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) - return + if aggStats.NumErrs > 0 { + fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) + } + return } avgThreadDur := aggStats.TotDuration / time.Duration(responders) //need to average the aggregated duration @@ -182,7 +184,9 @@ func main() { fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max())) fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) - fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) + if aggStats.NumErrs > 0 { + fmt.Printf("Error Counts:\t\t%v\n", mapToString(aggStats.ErrMap)) + } fmt.Printf("10%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.10))) fmt.Printf("50%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.50))) fmt.Printf("75%%:\t\t\t%v\n", toDuration(aggStats.Histogram.ValueAtPercentile(.75))) From 6e571f49e59ec9afdb2d0261dae4b3e99962b1ad Mon Sep 17 00:00:00 2001 From: robert engels Date: Fri, 5 Jul 2024 10:54:35 -0500 Subject: [PATCH 14/16] report overall request and transfer for entire test --- go-wrk.go | 9 +++++++++ loader/loader.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/go-wrk.go b/go-wrk.go index c4ed68e..4bea079 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -140,6 +140,8 @@ func main() { loadGen := loader.NewLoadCfg(duration, goroutines, testUrl, reqBody, method, host, header, statsAggregator, timeoutms, allowRedirectsFlag, disableCompression, disableKeepAlive, skipVerify, clientCert, clientKey, caCert, http2) + start := time.Now() + for i := 0; i < goroutines; i++ { go loadGen.RunSingleLoadSession() } @@ -165,6 +167,8 @@ func main() { } } + duration := time.Now().Sub(start) + if aggStats.NumRequests == 0 { fmt.Println("Error: No statistics collected / no requests found") fmt.Printf("Number of Errors:\t%v\n", aggStats.NumErrs) @@ -178,8 +182,13 @@ func main() { reqRate := float64(aggStats.NumRequests) / avgThreadDur.Seconds() bytesRate := float64(aggStats.TotRespSize) / avgThreadDur.Seconds() + + overallReqRate := float64(aggStats.NumRequests) / duration.Seconds() + overallBytesRate := float64(aggStats.TotRespSize) / duration.Seconds() + fmt.Printf("%v requests in %v, %v read\n", aggStats.NumRequests, avgThreadDur, util.ByteSize{float64(aggStats.TotRespSize)}) fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", reqRate, util.ByteSize{bytesRate}) + fmt.Printf("Overall Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", overallReqRate, util.ByteSize{overallBytesRate}) fmt.Printf("Fastest Request:\t%v\n", toDuration(aggStats.Histogram.Min())) fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max())) diff --git a/loader/loader.go b/loader/loader.go index 71f2f34..253c4be 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -41,7 +41,7 @@ type LoadCfg struct { http2 bool } -// RequesterStats used for colelcting aggregate statistics +// RequesterStats used for collecting aggregate statistics type RequesterStats struct { TotRespSize int64 TotDuration time.Duration From 780721c3b438bf1b89c965fc8968c22c83ab9908 Mon Sep 17 00:00:00 2001 From: robert engels Date: Fri, 5 Jul 2024 10:56:21 -0500 Subject: [PATCH 15/16] fix overall stats alignment --- go-wrk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-wrk.go b/go-wrk.go index 4bea079..b99c765 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -187,7 +187,7 @@ func main() { overallBytesRate := float64(aggStats.TotRespSize) / duration.Seconds() fmt.Printf("%v requests in %v, %v read\n", aggStats.NumRequests, avgThreadDur, util.ByteSize{float64(aggStats.TotRespSize)}) - fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", reqRate, util.ByteSize{bytesRate}) + fmt.Printf("Requests/sec:\t%.2f\nOverall Transfer/sec:\t%v\n", reqRate, util.ByteSize{bytesRate}) fmt.Printf("Overall Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", overallReqRate, util.ByteSize{overallBytesRate}) fmt.Printf("Fastest Request:\t%v\n", toDuration(aggStats.Histogram.Min())) fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) From 3608e2c6203537e086de32c92d052c592dcaf471 Mon Sep 17 00:00:00 2001 From: robert engels Date: Fri, 5 Jul 2024 10:58:36 -0500 Subject: [PATCH 16/16] fix stats labelling --- go-wrk.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go-wrk.go b/go-wrk.go index b99c765..3363ca9 100644 --- a/go-wrk.go +++ b/go-wrk.go @@ -187,8 +187,8 @@ func main() { overallBytesRate := float64(aggStats.TotRespSize) / duration.Seconds() fmt.Printf("%v requests in %v, %v read\n", aggStats.NumRequests, avgThreadDur, util.ByteSize{float64(aggStats.TotRespSize)}) - fmt.Printf("Requests/sec:\t%.2f\nOverall Transfer/sec:\t%v\n", reqRate, util.ByteSize{bytesRate}) - fmt.Printf("Overall Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", overallReqRate, util.ByteSize{overallBytesRate}) + fmt.Printf("Requests/sec:\t\t%.2f\nTransfer/sec:\t\t%v\n", reqRate, util.ByteSize{bytesRate}) + fmt.Printf("Overall Requests/sec:\t%.2f\nOverall Transfer/sec:\t%v\n", overallReqRate, util.ByteSize{overallBytesRate}) fmt.Printf("Fastest Request:\t%v\n", toDuration(aggStats.Histogram.Min())) fmt.Printf("Avg Req Time:\t\t%v\n", toDuration(int64(aggStats.Histogram.Mean()))) fmt.Printf("Slowest Request:\t%v\n", toDuration(aggStats.Histogram.Max()))