Skip to content

Commit

Permalink
Support benchmark fix #17
Browse files Browse the repository at this point in the history
  • Loading branch information
astaxie committed Apr 19, 2015
1 parent 5ed1f8a commit d7cae81
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 9 deletions.
20 changes: 18 additions & 2 deletions bat.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"strings"
)

const version = "0.0.1"
const version = "0.0.2"

var (
form bool
Expand All @@ -41,6 +41,9 @@ var (
auth string
proxy string
printV string
bench bool
benchN int
benchC int
isjson = flag.Bool("json", true, "Send the data as a JSON object")
method = flag.String("method", "GET", "HTTP method")
URL = flag.String("url", "", "HTTP request URL")
Expand All @@ -59,6 +62,10 @@ func init() {
flag.StringVar(&auth, "auth", "", "HTTP authentication username:password, USER[:PASS]")
flag.StringVar(&auth, "a", "", "HTTP authentication username:password, USER[:PASS]")
flag.StringVar(&proxy, "proxy", "", "Proxy host and port, PROXY_URL")
flag.BoolVar(&bench, "bench", false, "Sends bench requests to URL")
flag.BoolVar(&bench, "b", false, "Sends bench requests to URL")
flag.IntVar(&benchN, "b.N", 1000, "Number of requests to run")
flag.IntVar(&benchC, "b.C", 100, "Number of requests to run concurrently.")
jsonmap = make(map[string]interface{})
}

Expand Down Expand Up @@ -143,12 +150,18 @@ func main() {
}
httpreq.JsonBody(j)
}

// AB bench
if bench {
httpreq.Debug(false)
RunBench(httpreq)
return
}
res, err := httpreq.Response()
if err != nil {
log.Fatalln("can't get the url", err)
}

// download file
if download {
var fl string
if disposition := res.Header.Get("Content-Disposition"); disposition != "" {
Expand Down Expand Up @@ -273,6 +286,9 @@ Usage:
flags:
-a, -auth=USER[:PASS] Pass a username:password pair as the argument
-b, -bench=false Sends bench requests to URL
-b.N=1000 Number of requests to run
-b.C=100 Number of requests to run concurrently
-f, -form=false Submitting the data as a form
-j, -json=true Send the data in a JSON object
-p, -pretty=true Print Json Pretty Fomat
Expand Down
225 changes: 225 additions & 0 deletions bench.go
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)
}
}
23 changes: 16 additions & 7 deletions httplib/httplib.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
return b
}

// Dump Body.
func (b *BeegoHttpRequest) DumpBody(isdump bool) *BeegoHttpRequest {
b.setting.DumpBody = isdump
return b
}

// return the DumpRequest
func (b *BeegoHttpRequest) DumpRequest() []byte {
return b.dump
Expand Down Expand Up @@ -351,6 +357,15 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
if b.resp.StatusCode != 0 {
return b.resp, nil
}
resp, err := b.SendOut()
if err != nil {
return nil, err
}
b.resp = resp
return resp, nil
}

func (b *BeegoHttpRequest) SendOut() (*http.Response, error) {
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
Expand Down Expand Up @@ -420,13 +435,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
}
b.dump = dump
}

resp, err := client.Do(b.req)
if err != nil {
return nil, err
}
b.resp = resp
return resp, nil
return client.Do(b.req)
}

// String returns the body string in response.
Expand Down

0 comments on commit d7cae81

Please sign in to comment.