Skip to content

Commit d7cae81

Browse files
committed
Support benchmark fix #17
1 parent 5ed1f8a commit d7cae81

File tree

3 files changed

+259
-9
lines changed

3 files changed

+259
-9
lines changed

bat.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"strings"
3333
)
3434

35-
const version = "0.0.1"
35+
const version = "0.0.2"
3636

3737
var (
3838
form bool
@@ -41,6 +41,9 @@ var (
4141
auth string
4242
proxy string
4343
printV string
44+
bench bool
45+
benchN int
46+
benchC int
4447
isjson = flag.Bool("json", true, "Send the data as a JSON object")
4548
method = flag.String("method", "GET", "HTTP method")
4649
URL = flag.String("url", "", "HTTP request URL")
@@ -59,6 +62,10 @@ func init() {
5962
flag.StringVar(&auth, "auth", "", "HTTP authentication username:password, USER[:PASS]")
6063
flag.StringVar(&auth, "a", "", "HTTP authentication username:password, USER[:PASS]")
6164
flag.StringVar(&proxy, "proxy", "", "Proxy host and port, PROXY_URL")
65+
flag.BoolVar(&bench, "bench", false, "Sends bench requests to URL")
66+
flag.BoolVar(&bench, "b", false, "Sends bench requests to URL")
67+
flag.IntVar(&benchN, "b.N", 1000, "Number of requests to run")
68+
flag.IntVar(&benchC, "b.C", 100, "Number of requests to run concurrently.")
6269
jsonmap = make(map[string]interface{})
6370
}
6471

@@ -143,12 +150,18 @@ func main() {
143150
}
144151
httpreq.JsonBody(j)
145152
}
146-
153+
// AB bench
154+
if bench {
155+
httpreq.Debug(false)
156+
RunBench(httpreq)
157+
return
158+
}
147159
res, err := httpreq.Response()
148160
if err != nil {
149161
log.Fatalln("can't get the url", err)
150162
}
151163

164+
// download file
152165
if download {
153166
var fl string
154167
if disposition := res.Header.Get("Content-Disposition"); disposition != "" {
@@ -273,6 +286,9 @@ Usage:
273286
274287
flags:
275288
-a, -auth=USER[:PASS] Pass a username:password pair as the argument
289+
-b, -bench=false Sends bench requests to URL
290+
-b.N=1000 Number of requests to run
291+
-b.C=100 Number of requests to run concurrently
276292
-f, -form=false Submitting the data as a form
277293
-j, -json=true Send the data in a JSON object
278294
-p, -pretty=true Print Json Pretty Fomat

bench.go

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
"sort"
7+
"strings"
8+
"sync"
9+
"time"
10+
11+
"github.com/astaxie/bat/httplib"
12+
)
13+
14+
type result struct {
15+
err error
16+
statusCode int
17+
duration time.Duration
18+
contentLength int64
19+
}
20+
21+
func RunBench(b *httplib.BeegoHttpRequest) {
22+
runtime.GOMAXPROCS(runtime.NumCPU())
23+
start := time.Now()
24+
results := make(chan *result, benchN)
25+
var wg sync.WaitGroup
26+
wg.Add(benchN)
27+
28+
jobs := make(chan int, benchN)
29+
for i := 0; i < benchC; i++ {
30+
go func() {
31+
worker(&wg, jobs, results, b)
32+
}()
33+
}
34+
for i := 0; i < benchN; i++ {
35+
jobs <- i
36+
}
37+
close(jobs)
38+
39+
wg.Wait()
40+
printReport(benchN, results, "", time.Now().Sub(start))
41+
close(results)
42+
}
43+
44+
func worker(wg *sync.WaitGroup, ch chan int, results chan *result, b *httplib.BeegoHttpRequest) {
45+
for _ = range ch {
46+
s := time.Now()
47+
code := 0
48+
size := int64(0)
49+
resp, err := b.SendOut()
50+
if err == nil {
51+
size = resp.ContentLength
52+
code = resp.StatusCode
53+
resp.Body.Close()
54+
}
55+
wg.Done()
56+
57+
results <- &result{
58+
statusCode: code,
59+
duration: time.Now().Sub(s),
60+
err: err,
61+
contentLength: size,
62+
}
63+
}
64+
}
65+
66+
const (
67+
barChar = "∎"
68+
)
69+
70+
type report struct {
71+
avgTotal float64
72+
fastest float64
73+
slowest float64
74+
average float64
75+
rps float64
76+
77+
results chan *result
78+
total time.Duration
79+
80+
errorDist map[string]int
81+
statusCodeDist map[int]int
82+
lats []float64
83+
sizeTotal int64
84+
85+
output string
86+
}
87+
88+
func printReport(size int, results chan *result, output string, total time.Duration) {
89+
r := &report{
90+
output: output,
91+
results: results,
92+
total: total,
93+
statusCodeDist: make(map[int]int),
94+
errorDist: make(map[string]int),
95+
}
96+
r.finalize()
97+
}
98+
99+
func (r *report) finalize() {
100+
for {
101+
select {
102+
case res := <-r.results:
103+
if res.err != nil {
104+
r.errorDist[res.err.Error()]++
105+
} else {
106+
r.lats = append(r.lats, res.duration.Seconds())
107+
r.avgTotal += res.duration.Seconds()
108+
r.statusCodeDist[res.statusCode]++
109+
if res.contentLength > 0 {
110+
r.sizeTotal += res.contentLength
111+
}
112+
}
113+
default:
114+
r.rps = float64(len(r.lats)) / r.total.Seconds()
115+
r.average = r.avgTotal / float64(len(r.lats))
116+
r.print()
117+
return
118+
}
119+
}
120+
}
121+
122+
func (r *report) print() {
123+
sort.Float64s(r.lats)
124+
125+
if r.output == "csv" {
126+
r.printCSV()
127+
return
128+
}
129+
130+
if len(r.lats) > 0 {
131+
r.fastest = r.lats[0]
132+
r.slowest = r.lats[len(r.lats)-1]
133+
fmt.Printf("\nSummary:\n")
134+
fmt.Printf(" Total:\t%4.4f secs.\n", r.total.Seconds())
135+
fmt.Printf(" Slowest:\t%4.4f secs.\n", r.slowest)
136+
fmt.Printf(" Fastest:\t%4.4f secs.\n", r.fastest)
137+
fmt.Printf(" Average:\t%4.4f secs.\n", r.average)
138+
fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps)
139+
if r.sizeTotal > 0 {
140+
fmt.Printf(" Total Data Received:\t%d bytes.\n", r.sizeTotal)
141+
fmt.Printf(" Response Size per Request:\t%d bytes.\n", r.sizeTotal/int64(len(r.lats)))
142+
}
143+
r.printStatusCodes()
144+
r.printHistogram()
145+
r.printLatencies()
146+
}
147+
148+
if len(r.errorDist) > 0 {
149+
r.printErrors()
150+
}
151+
}
152+
153+
func (r *report) printCSV() {
154+
for i, val := range r.lats {
155+
fmt.Printf("%v,%4.4f\n", i+1, val)
156+
}
157+
}
158+
159+
// Prints percentile latencies.
160+
func (r *report) printLatencies() {
161+
pctls := []int{10, 25, 50, 75, 90, 95, 99}
162+
data := make([]float64, len(pctls))
163+
j := 0
164+
for i := 0; i < len(r.lats) && j < len(pctls); i++ {
165+
current := i * 100 / len(r.lats)
166+
if current >= pctls[j] {
167+
data[j] = r.lats[i]
168+
j++
169+
}
170+
}
171+
fmt.Printf("\nLatency distribution:\n")
172+
for i := 0; i < len(pctls); i++ {
173+
if data[i] > 0 {
174+
fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i])
175+
}
176+
}
177+
}
178+
179+
func (r *report) printHistogram() {
180+
bc := 10
181+
buckets := make([]float64, bc+1)
182+
counts := make([]int, bc+1)
183+
bs := (r.slowest - r.fastest) / float64(bc)
184+
for i := 0; i < bc; i++ {
185+
buckets[i] = r.fastest + bs*float64(i)
186+
}
187+
buckets[bc] = r.slowest
188+
var bi int
189+
var max int
190+
for i := 0; i < len(r.lats); {
191+
if r.lats[i] <= buckets[bi] {
192+
i++
193+
counts[bi]++
194+
if max < counts[bi] {
195+
max = counts[bi]
196+
}
197+
} else if bi < len(buckets)-1 {
198+
bi++
199+
}
200+
}
201+
fmt.Printf("\nResponse time histogram:\n")
202+
for i := 0; i < len(buckets); i++ {
203+
// Normalize bar lengths.
204+
var barLen int
205+
if max > 0 {
206+
barLen = counts[i] * 40 / max
207+
}
208+
fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen))
209+
}
210+
}
211+
212+
// Prints status code distribution.
213+
func (r *report) printStatusCodes() {
214+
fmt.Printf("\nStatus code distribution:\n")
215+
for code, num := range r.statusCodeDist {
216+
fmt.Printf(" [%d]\t%d responses\n", code, num)
217+
}
218+
}
219+
220+
func (r *report) printErrors() {
221+
fmt.Printf("\nError distribution:\n")
222+
for err, num := range r.errorDist {
223+
fmt.Printf(" [%d]\t%s\n", num, err)
224+
}
225+
}

httplib/httplib.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
180180
return b
181181
}
182182

183+
// Dump Body.
184+
func (b *BeegoHttpRequest) DumpBody(isdump bool) *BeegoHttpRequest {
185+
b.setting.DumpBody = isdump
186+
return b
187+
}
188+
183189
// return the DumpRequest
184190
func (b *BeegoHttpRequest) DumpRequest() []byte {
185191
return b.dump
@@ -351,6 +357,15 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
351357
if b.resp.StatusCode != 0 {
352358
return b.resp, nil
353359
}
360+
resp, err := b.SendOut()
361+
if err != nil {
362+
return nil, err
363+
}
364+
b.resp = resp
365+
return resp, nil
366+
}
367+
368+
func (b *BeegoHttpRequest) SendOut() (*http.Response, error) {
354369
var paramBody string
355370
if len(b.params) > 0 {
356371
var buf bytes.Buffer
@@ -420,13 +435,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
420435
}
421436
b.dump = dump
422437
}
423-
424-
resp, err := client.Do(b.req)
425-
if err != nil {
426-
return nil, err
427-
}
428-
b.resp = resp
429-
return resp, nil
438+
return client.Do(b.req)
430439
}
431440

432441
// String returns the body string in response.

0 commit comments

Comments
 (0)