Skip to content

Commit 322135f

Browse files
committed
This commit adds some tests
1 parent c86fabb commit 322135f

16 files changed

+1302
-89
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ _testmain.go
2222
*.exe
2323
*.test
2424
*.prof
25+
cover.*

args_parser.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"os"
10+
"time"
11+
)
12+
13+
var (
14+
errNoURL = errors.New("No URL supplied")
15+
errTooManyArgs = errors.New("Too many arguments are supplied")
16+
17+
emptyConf = config{}
18+
parser = newDefaultParser()
19+
)
20+
21+
type defaultParser struct {
22+
fs *flag.FlagSet
23+
24+
numReqs *nullableUint64
25+
duration *nullableDuration
26+
headers *headersList
27+
numConns uint64
28+
timeout time.Duration
29+
latencies bool
30+
method string
31+
body string
32+
}
33+
34+
func newDefaultParser() *defaultParser {
35+
dp := new(defaultParser)
36+
dp.fs = flag.NewFlagSet(programName, flag.ContinueOnError)
37+
dp.numReqs = new(nullableUint64)
38+
dp.duration = new(nullableDuration)
39+
dp.headers = new(headersList)
40+
dp.fs.Uint64Var(&dp.numConns, "c", defaultNumberOfConns, "Maximum number of concurrent connections")
41+
dp.fs.DurationVar(&dp.timeout, "timeout", defaultTimeout, "Socket/request timeout")
42+
dp.fs.BoolVar(&dp.latencies, "latencies", false, "Print latency statistics")
43+
dp.fs.StringVar(&dp.method, "m", "GET", "Request method")
44+
dp.fs.StringVar(&dp.body, "data", "", "Request body")
45+
dp.fs.Var(dp.headers, "H", "HTTP headers to use")
46+
dp.fs.Var(dp.numReqs, "n", "Number of requests")
47+
dp.fs.Var(dp.duration, "d", "Duration of test")
48+
return dp
49+
}
50+
51+
func (p *defaultParser) usage(out io.Writer) {
52+
fmt.Fprintln(out, programName, "<options> <url>")
53+
p.fs.SetOutput(out)
54+
p.fs.PrintDefaults()
55+
}
56+
57+
func (p *defaultParser) parse(args []string) (config, error) {
58+
err := p.parseArgs(args)
59+
if err != nil {
60+
return emptyConf, err
61+
}
62+
if p.fs.NArg() == 0 {
63+
return emptyConf, errNoURL
64+
}
65+
if p.fs.NArg() > 1 {
66+
return emptyConf, errTooManyArgs
67+
}
68+
return config{
69+
numConns: p.numConns,
70+
numReqs: p.numReqs.val,
71+
duration: p.duration.val,
72+
url: p.fs.Arg(0),
73+
headers: p.headers,
74+
timeout: p.timeout,
75+
method: p.method,
76+
body: p.body,
77+
printLatencies: p.latencies,
78+
}, nil
79+
}
80+
81+
func (p *defaultParser) parseArgs(args []string) error {
82+
p.fs.SetOutput(ioutil.Discard)
83+
err := p.fs.Parse(args[1:])
84+
p.fs.SetOutput(os.Stdout)
85+
return err
86+
}

args_parser_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestArgsParsing(t *testing.T) {
9+
expectations := []struct {
10+
p *defaultParser
11+
in []string
12+
out error
13+
}{
14+
{
15+
newDefaultParser(),
16+
[]string{programName},
17+
errNoURL,
18+
},
19+
{
20+
newDefaultParser(),
21+
[]string{programName, "http://google.com", "http://yahoo.com"},
22+
errTooManyArgs,
23+
},
24+
{
25+
newDefaultParser(),
26+
[]string{programName, "http://google.com"},
27+
nil,
28+
},
29+
}
30+
for _, e := range expectations {
31+
if _, err := e.p.parse(e.in); err != e.out {
32+
t.Log(err, e.out)
33+
t.Fail()
34+
}
35+
}
36+
}
37+
38+
func TestUnspecifiedArgParsing(t *testing.T) {
39+
p := newDefaultParser()
40+
args := []string{programName, "--someunspecifiedflag"}
41+
_, err := p.parse(args)
42+
if err == nil {
43+
t.Fail()
44+
}
45+
}
46+
47+
func TestUsagePrinting(t *testing.T) {
48+
b := new(bytes.Buffer)
49+
p := newDefaultParser()
50+
p.usage(b)
51+
if b.Len() == 0 {
52+
t.Fail()
53+
}
54+
}

bombardier.go

+63-67
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package main
22

33
import (
4-
"flag"
54
"fmt"
5+
"io"
66
"io/ioutil"
77
"os"
88
"sync"
@@ -13,20 +13,16 @@ import (
1313
"github.com/valyala/fasthttp"
1414
)
1515

16-
const (
17-
maxRps = 10000000
18-
requestsInterval = 100 * time.Millisecond
19-
)
20-
2116
type bombardier struct {
2217
conf config
2318
requestHeaders *fasthttp.RequestHeader
2419
barrier completionBarrier
2520

26-
bytesWritten int64
27-
timeTaken time.Duration
28-
latencies *stats
29-
requests *stats
21+
bytesTotal int64
22+
bytesData int64
23+
timeTaken time.Duration
24+
latencies *stats
25+
requests *stats
3026

3127
client *fasthttp.Client
3228
done chan bool
@@ -46,6 +42,9 @@ type bombardier struct {
4642

4743
// Progress bar
4844
bar *pb.ProgressBar
45+
46+
// Output
47+
out io.Writer
4948
}
5049

5150
func newBombardier(c config) (*bombardier, error) {
@@ -65,10 +64,11 @@ func newBombardier(c config) (*bombardier, error) {
6564
b.bar = pb.New(int(b.conf.duration.Seconds()))
6665
b.bar.ShowCounters = false
6766
b.bar.ShowPercent = false
68-
b.barrier = newTimedCompletionBarrier(int(c.numConns), *c.duration, func() {
67+
b.barrier = newTimedCompletionBarrier(c.numConns, tickDuration, *c.duration, func() {
6968
b.bar.Increment()
7069
})
7170
}
71+
b.out = os.Stdout
7272
b.client = &fasthttp.Client{
7373
MaxConnsPerHost: int(c.numConns),
7474
}
@@ -89,15 +89,16 @@ func (b *bombardier) prepareRequest() (*fasthttp.Request, *fasthttp.Response) {
8989
return req, resp
9090
}
9191

92-
func (b *bombardier) fireRequest(req *fasthttp.Request, resp *fasthttp.Response) (bytesWritten int64, code int, msTaken uint64) {
92+
func (b *bombardier) fireRequest(req *fasthttp.Request, resp *fasthttp.Response) (bytesData, bytesTotal int64, code int, msTaken uint64) {
9393
start := time.Now()
9494
err := b.client.DoTimeout(req, resp, b.conf.timeout)
9595
if err != nil {
9696
code = 0
9797
} else {
9898
code = resp.StatusCode()
9999
}
100-
bytesWritten, _ = resp.WriteTo(ioutil.Discard)
100+
bytesData = int64(len(resp.Body()))
101+
bytesTotal, _ = resp.WriteTo(ioutil.Discard)
101102
msTaken = uint64(time.Since(start).Nanoseconds() / 1000)
102103
return
103104
}
@@ -107,9 +108,10 @@ func (b *bombardier) releaseRequest(req *fasthttp.Request, resp *fasthttp.Respon
107108
fasthttp.ReleaseResponse(resp)
108109
}
109110

110-
func (b *bombardier) writeStatistics(bytesWritten int64, code int, msTaken uint64) {
111+
func (b *bombardier) writeStatistics(bytesData, bytesTotal int64, code int, msTaken uint64) {
111112
b.latencies.record(msTaken)
112-
atomic.AddInt64(&b.bytesWritten, bytesWritten)
113+
atomic.AddInt64(&b.bytesTotal, bytesTotal)
114+
atomic.AddInt64(&b.bytesData, bytesData)
113115
b.rpl.Lock()
114116
b.reqs++
115117
b.rpl.Unlock()
@@ -133,12 +135,12 @@ func (b *bombardier) writeStatistics(bytesWritten int64, code int, msTaken uint6
133135
atomic.AddUint64(counter, 1)
134136
}
135137

136-
func (b *bombardier) Worker() {
138+
func (b *bombardier) worker() {
137139
for b.barrier.grabWork() {
138140
req, resp := b.prepareRequest()
139-
bytesWritten, code, msTaken := b.fireRequest(req, resp)
141+
bytesData, bytesTotal, code, msTaken := b.fireRequest(req, resp)
140142
b.releaseRequest(req, resp)
141-
b.writeStatistics(bytesWritten, code, msTaken)
143+
b.writeStatistics(bytesData, bytesTotal, code, msTaken)
142144
b.barrier.jobDone()
143145
}
144146
}
@@ -174,94 +176,88 @@ func (b *bombardier) bombard() {
174176
bombardmentBegin := time.Now()
175177
b.start = time.Now()
176178
for i := uint64(0); i < b.conf.numConns; i++ {
177-
go b.Worker()
179+
go b.worker()
178180
}
179181
go b.rateMeter()
180182
b.barrier.wait()
181183
b.timeTaken = time.Since(bombardmentBegin)
182184
b.done <- true
183185
<-b.done
184-
b.bar.FinishPrint("Done!")
186+
b.bar.Finish()
187+
fmt.Fprintln(b.out, "Done!")
185188
}
186189

187190
func (b *bombardier) throughput() float64 {
188-
return float64(b.bytesWritten) / b.timeTaken.Seconds()
191+
return float64(b.bytesTotal) / b.timeTaken.Seconds()
189192
}
190193

191194
func (b *bombardier) printIntro() {
192195
if b.conf.testType == counted {
193-
fmt.Printf("Bombarding %v with %v requests using %v connections\n",
196+
fmt.Fprintf(b.out, "Bombarding %v with %v requests using %v connections\n",
194197
b.conf.url, *b.conf.numReqs, b.conf.numConns)
195198
} else if b.conf.testType == timed {
196-
fmt.Printf("Bombarding %v for %v using %v connections\n",
199+
fmt.Fprintf(b.out, "Bombarding %v for %v using %v connections\n",
197200
b.conf.url, *b.conf.duration, b.conf.numConns)
198201
}
199202
}
200203

201204
func (b *bombardier) printLatencyStats() {
202205
percentiles := []float64{50.0, 75.0, 90.0, 99.0}
203-
fmt.Println(" Latency Distribution")
206+
fmt.Fprintln(b.out, " Latency Distribution")
204207
for i := 0; i < len(percentiles); i++ {
205208
p := percentiles[i]
206209
n := b.latencies.percentile(p)
207-
fmt.Printf(" %2.0f%% %10s", p, formatUnits(float64(n), timeUnitsUs, 2))
208-
fmt.Printf("\n")
210+
fmt.Fprintf(b.out, " %2.0f%% %10s", p, formatUnits(float64(n), timeUnitsUs, 2))
211+
fmt.Fprintf(b.out, "\n")
209212
}
210213
}
211214

212215
func (b *bombardier) printStats() {
213-
fmt.Printf("%10v %10v %10v %10v\n", "Statistics", "Avg", "Stdev", "Max")
214-
fmt.Println(rpsString(b.requests))
215-
fmt.Println(latenciesString(b.latencies))
216-
if *latencies {
216+
fmt.Fprintf(b.out, "%10v %10v %10v %10v\n", "Statistics", "Avg", "Stdev", "Max")
217+
fmt.Fprintln(b.out, rpsString(b.requests))
218+
fmt.Fprintln(b.out, latenciesString(b.latencies))
219+
if b.conf.printLatencies {
217220
b.printLatencyStats()
218221
}
219-
fmt.Println(" HTTP codes:")
220-
fmt.Printf(" 1xx - %v, 2xx - %v, 3xx - %v, 4xx - %v, 5xx - %v\n",
222+
fmt.Fprintln(b.out, " HTTP codes:")
223+
fmt.Fprintf(b.out, " 1xx - %v, 2xx - %v, 3xx - %v, 4xx - %v, 5xx - %v\n",
221224
b.req1xx, b.req2xx, b.req3xx, b.req4xx, b.req5xx)
222-
fmt.Printf(" errored - %v\n", b.errored)
223-
fmt.Printf(" %-10v %10v/s\n", "Throughput:", formatBinary(b.throughput()))
225+
fmt.Fprintf(b.out, " errored - %v\n", b.errored)
226+
fmt.Fprintf(b.out, " %-10v %10v/s\n", "Throughput:", formatBinary(b.throughput()))
227+
}
228+
229+
func (b *bombardier) redirectOutputTo(out io.Writer) {
230+
b.bar.Output = out
231+
b.out = out
232+
}
233+
234+
func (b *bombardier) disableOutput() {
235+
b.redirectOutputTo(ioutil.Discard)
236+
b.bar.NotPrint = true
224237
}
225238

226-
var (
227-
numReqs = new(nullableUint64)
228-
duration = new(nullableDuration)
229-
headers = new(headersList)
230-
numConns = flag.Uint64("c", 200, "Maximum number of concurrent connections")
231-
timeout = flag.Duration("timeout", 2*time.Second, "Socket/request timeout")
232-
latencies = flag.Bool("latencies", false, "Print latency statistics")
233-
method = flag.String("m", "GET", "Request method")
234-
body = flag.String("data", "", "Request body")
239+
const (
240+
maxRps = 10000000
241+
requestsInterval = 100 * time.Millisecond
242+
tickDuration = 1 * time.Second
243+
defaultTimeout = 2 * time.Second
244+
245+
programName = "bombardier"
246+
247+
EXIT_FAILURE = 1
235248
)
236249

237250
func main() {
238-
flag.Var(headers, "H", "HTTP headers to use")
239-
flag.Var(numReqs, "n", "Number of requests")
240-
flag.Var(duration, "d", "Duration of test")
241-
flag.Parse()
242-
if flag.NArg() == 0 {
243-
fmt.Println("No URL supplied")
244-
flag.Usage()
245-
os.Exit(1)
246-
}
247-
if flag.NArg() > 1 {
248-
fmt.Println("Too many arguments are supplied")
249-
os.Exit(1)
251+
config, err := parser.parse(os.Args)
252+
if err != nil {
253+
fmt.Println(err)
254+
parser.usage(os.Stdout)
255+
os.Exit(EXIT_FAILURE)
250256
}
251-
bombardier, err := newBombardier(
252-
config{
253-
numConns: *numConns,
254-
numReqs: numReqs.val,
255-
duration: duration.val,
256-
url: flag.Arg(0),
257-
headers: headers,
258-
timeout: *timeout,
259-
method: *method,
260-
body: *body,
261-
})
257+
bombardier, err := newBombardier(config)
262258
if err != nil {
263259
fmt.Println(err)
264-
os.Exit(1)
260+
os.Exit(EXIT_FAILURE)
265261
}
266262
bombardier.bombard()
267263
bombardier.printStats()

0 commit comments

Comments
 (0)