-
Notifications
You must be signed in to change notification settings - Fork 221
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
259 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters