package main import ( "fmt" "runtime" "sort" "strings" "sync" "time" "github.com/astaxie/bat/httplib" ) type result struct { err error statusCode int duration time.Duration contentLength int64 } func RunBench(b *httplib.BeegoHttpRequest) { runtime.GOMAXPROCS(runtime.NumCPU()) start := time.Now() results := make(chan *result, benchN) var wg sync.WaitGroup wg.Add(benchN) jobs := make(chan int, benchN) for i := 0; i < benchC; i++ { go func() { worker(&wg, jobs, results, b) }() } for i := 0; i < benchN; i++ { jobs <- i } close(jobs) wg.Wait() printReport(benchN, results, "", time.Now().Sub(start)) close(results) } func worker(wg *sync.WaitGroup, ch chan int, results chan *result, b *httplib.BeegoHttpRequest) { for _ = range ch { s := time.Now() code := 0 size := int64(0) resp, err := b.SendOut() if err == nil { size = resp.ContentLength code = resp.StatusCode resp.Body.Close() } wg.Done() results <- &result{ statusCode: code, duration: time.Now().Sub(s), err: err, contentLength: size, } } } const ( barChar = "∎" ) type report struct { avgTotal float64 fastest float64 slowest float64 average float64 rps float64 results chan *result total time.Duration errorDist map[string]int statusCodeDist map[int]int lats []float64 sizeTotal int64 output string } func printReport(size int, results chan *result, output string, total time.Duration) { r := &report{ output: output, results: results, total: total, statusCodeDist: make(map[int]int), errorDist: make(map[string]int), } r.finalize() } func (r *report) finalize() { for { select { case res := <-r.results: if res.err != nil { r.errorDist[res.err.Error()]++ } else { r.lats = append(r.lats, res.duration.Seconds()) r.avgTotal += res.duration.Seconds() r.statusCodeDist[res.statusCode]++ if res.contentLength > 0 { r.sizeTotal += res.contentLength } } default: r.rps = float64(len(r.lats)) / r.total.Seconds() r.average = r.avgTotal / float64(len(r.lats)) r.print() return } } } func (r *report) print() { sort.Float64s(r.lats) if r.output == "csv" { r.printCSV() return } if len(r.lats) > 0 { r.fastest = r.lats[0] r.slowest = r.lats[len(r.lats)-1] fmt.Printf("\nSummary:\n") fmt.Printf(" Total:\t%4.4f secs.\n", r.total.Seconds()) fmt.Printf(" Slowest:\t%4.4f secs.\n", r.slowest) fmt.Printf(" Fastest:\t%4.4f secs.\n", r.fastest) fmt.Printf(" Average:\t%4.4f secs.\n", r.average) fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps) if r.sizeTotal > 0 { fmt.Printf(" Total Data Received:\t%d bytes.\n", r.sizeTotal) fmt.Printf(" Response Size per Request:\t%d bytes.\n", r.sizeTotal/int64(len(r.lats))) } r.printStatusCodes() r.printHistogram() r.printLatencies() } if len(r.errorDist) > 0 { r.printErrors() } } func (r *report) printCSV() { for i, val := range r.lats { fmt.Printf("%v,%4.4f\n", i+1, val) } } // Prints percentile latencies. func (r *report) printLatencies() { pctls := []int{10, 25, 50, 75, 90, 95, 99} data := make([]float64, len(pctls)) j := 0 for i := 0; i < len(r.lats) && j < len(pctls); i++ { current := i * 100 / len(r.lats) if current >= pctls[j] { data[j] = r.lats[i] j++ } } fmt.Printf("\nLatency distribution:\n") for i := 0; i < len(pctls); i++ { if data[i] > 0 { fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i]) } } } func (r *report) printHistogram() { bc := 10 buckets := make([]float64, bc+1) counts := make([]int, bc+1) bs := (r.slowest - r.fastest) / float64(bc) for i := 0; i < bc; i++ { buckets[i] = r.fastest + bs*float64(i) } buckets[bc] = r.slowest var bi int var max int for i := 0; i < len(r.lats); { if r.lats[i] <= buckets[bi] { i++ counts[bi]++ if max < counts[bi] { max = counts[bi] } } else if bi < len(buckets)-1 { bi++ } } fmt.Printf("\nResponse time histogram:\n") for i := 0; i < len(buckets); i++ { // Normalize bar lengths. var barLen int if max > 0 { barLen = counts[i] * 40 / max } fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen)) } } // Prints status code distribution. func (r *report) printStatusCodes() { fmt.Printf("\nStatus code distribution:\n") for code, num := range r.statusCodeDist { fmt.Printf(" [%d]\t%d responses\n", code, num) } } func (r *report) printErrors() { fmt.Printf("\nError distribution:\n") for err, num := range r.errorDist { fmt.Printf(" [%d]\t%s\n", num, err) } }