Skip to content

Commit

Permalink
This is pretty much v0.2.
Browse files Browse the repository at this point in the history
Now there are two modes in which you can run the tool. First is the timed
one, which is default(10s test with 200 conns). Second is the good old
"How many times do I shoot that thing?".
Also, I thought it would be useful to be able to supply request's method
and body. Oh, and the code was reorganized. That's it. For more details
just read the diff.
  • Loading branch information
codesenberg committed Jun 20, 2016
1 parent bb3c827 commit 45f56ed
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 109 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Copyright (c) 2016 Максим Федосеев
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

Expand Down
50 changes: 28 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
bombardier is a HTTP(S) benchmarking tool. It's written in Go programming language and uses excellent [fasthttp](https://github.com/valyala/fasthttp) instead of Go's default http library, because of it's lightning fast performance.

##Installation
You are encourages to grab the latest version of the tool in the [releases](https://github.com/bugsenberg/bombardier/releases) section and test the tool by yourself.
You cat grab the latest version in the [releases](https://github.com/codesenberg/bombardier/releases) section.

#### If you can't find your OS/ARCH combo(aka build from the source)
This one is actually pretty straightforward. Just run `go get github.com/bugsenberg/bombardier`(but you may need to get the deps first).
This one is actually pretty straightforward. Just run `go get github.com/codesenberg/bombardier`.

##Usage
Run it like:
Expand All @@ -15,44 +15,50 @@ bombardier <options> <url>
Also, you can supply these options:
```
-H value
HTTP headers to use (default [])
-c int
HTTP headers to use
-c uint
Maximum number of concurrent connections (default 200)
-n value
Number of requests
-d value
Duration of test
-data string
Request body
-latencies
Print latency statistics
-n int
Number of requests (default 10000)
-m string
Request method (default "GET")
-timeout duration
Socket/request timeout (default 2s)
```
You should see something like this if you done everything correctly:
```
> bombardier -c 200 -n 10000000 http://localhost:8080
Bombarding http://localhost:8080 with 10000000 requests using 200 connections
10000000 / 10000000 [============================================] 100.00 % 55s
10000000 / 10000000 [============================================] 100.00 % 47s Done!
Statistics Avg Stdev Max
Reqs/sec 181631.00 13494.01 197924
Latency 1.10ms 319.69us 82.51ms
Reqs/sec 209655.00 9914.22 216847
Latency 0.95ms 292.09us 37.00ms
HTTP codes:
1xx - 0, 2xx - 10000000, 3xx - 0, 4xx - 0, 5xx - 0
errored - 0
Throughput: 201.11MB/s
Throughput: 232.12MB/s
```
Or, on a realworld server(with latency distribution):
```
> bombardier -c 200 -n 10000 --latencies http://google.com
Bombarding http://google.com with 10000 requests using 200 connections
10000 / 10000 [===================================================] 100.00 % 2s
> bombardier -c 200 -d 10s --latencies http://google.com
Bombarding http://google.com for 10s using 200 connections
[==========================================================================]10s Done!
Statistics Avg Stdev Max
Reqs/sec 4165.00 1382.95 4939
Latency 43.14ms 26.01ms 394.05ms
Reqs/sec 5384.00 789.97 5699
Latency 36.96ms 19.58ms 1.44s
Latency Distribution
50% 38.50ms
75% 44.01ms
90% 47.01ms
99% 113.01ms
50% 34.00ms
75% 41.00ms
90% 42.00ms
99% 45.00ms
HTTP codes:
1xx - 0, 2xx - 0, 3xx - 9994, 4xx - 0, 5xx - 0
errored - 6
Throughput: 1.95MB/s
1xx - 0, 2xx - 0, 3xx - 54083, 4xx - 0, 5xx - 0
errored - 2
Throughput: 2.51MB/s
```
157 changes: 71 additions & 86 deletions bombardier.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package main

import (
"errors"
"flag"
"fmt"
"io/ioutil"
"net/url"
"os"
"sync"
"sync/atomic"
Expand All @@ -21,20 +19,15 @@ const (
)

type bombardier struct {
numReqs int
numConns int
url string
conf config
requestHeaders *fasthttp.RequestHeader
timeout time.Duration

reqsDone uint64
barrier completionBarrier

bytesWritten int64
timeTaken time.Duration
latencies *stats
requests *stats

jobs sync.WaitGroup
client *fasthttp.Client
done chan bool

Expand All @@ -55,56 +48,50 @@ type bombardier struct {
bar *pb.ProgressBar
}

func newBombardier(numConns, numReqs int, url string, headers *headersList, timeout time.Duration) (*bombardier, error) {
b := new(bombardier)
b.numReqs = numReqs
b.numConns = numConns
b.url = url
b.timeout = timeout
if err := b.checkArgs(); err != nil {
func newBombardier(c config) (*bombardier, error) {
if err := c.checkArgs(); err != nil {
return nil, err
}
b.latencies = newStats(b.timeout.Nanoseconds() / 1000)
b := new(bombardier)
b.conf = c
b.latencies = newStats(c.timeoutMillis())
b.requests = newStats(maxRps)
b.jobs.Add(b.numReqs)
if b.conf.testType == counted {
b.bar = pb.New64(int64(*b.conf.numReqs))
b.barrier = newCountingCompletionBarrier(*c.numReqs, func() {
b.bar.Increment()
})
} else if b.conf.testType == timed {
b.bar = pb.New(int(b.conf.duration.Seconds()))
b.bar.ShowCounters = false
b.bar.ShowPercent = false
b.barrier = newTimedCompletionBarrier(int(c.numConns), *c.duration, func() {
b.bar.Increment()
})
}
b.client = &fasthttp.Client{
MaxConnsPerHost: b.numConns,
MaxConnsPerHost: int(c.numConns),
}
b.done = make(chan bool)
b.requestHeaders = headers.toRequestHeader()
b.requestHeaders = c.requestHeaders()
return b, nil
}

func (b *bombardier) checkArgs() error {
if b.numReqs < 1 {
return errors.New("Invalid number of requests(must be > 0)")
}
if b.numConns < 1 {
return errors.New("Invalid number of connections(must be > 0)")
}
if b.timeout < 0 {
return errors.New("Timeout can't be negative")
}
if b.timeout > 10*time.Second {
return errors.New("Timeout is too big(more that 10s)")
}
return nil
}

func (b *bombardier) prepareRequest(headers *fasthttp.RequestHeader) (*fasthttp.Request, *fasthttp.Response) {
func (b *bombardier) prepareRequest() (*fasthttp.Request, *fasthttp.Response) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
if headers != nil {
headers.CopyTo(&req.Header)
if b.requestHeaders != nil {
b.requestHeaders.CopyTo(&req.Header)
}
req.Header.SetMethod("GET")
req.SetRequestURI(b.url)
req.Header.SetMethod(b.conf.method)
req.SetRequestURI(b.conf.url)
req.SetBodyString(b.conf.body)
return req, resp
}

func (b *bombardier) fireRequest(req *fasthttp.Request, resp *fasthttp.Response) (bytesWritten int64, code int, msTaken uint64) {
start := time.Now()
err := b.client.DoTimeout(req, resp, b.timeout)
err := b.client.DoTimeout(req, resp, b.conf.timeout)
if err != nil {
code = 0
} else {
Expand Down Expand Up @@ -146,23 +133,13 @@ func (b *bombardier) writeStatistics(bytesWritten int64, code int, msTaken uint6
atomic.AddUint64(counter, 1)
}

func (b *bombardier) grabWork() bool {
reqID := atomic.AddUint64(&b.reqsDone, 1)
return reqID <= uint64(b.numReqs)
}

func (b *bombardier) reportDone() {
b.bar.Increment()
b.jobs.Done()
}

func (b *bombardier) Worker(headers *fasthttp.RequestHeader) {
for b.grabWork() {
req, resp := b.prepareRequest(headers)
func (b *bombardier) Worker() {
for b.barrier.grabWork() {
req, resp := b.prepareRequest()
bytesWritten, code, msTaken := b.fireRequest(req, resp)
b.releaseRequest(req, resp)
b.writeStatistics(bytesWritten, code, msTaken)
b.reportDone()
b.barrier.jobDone()
}
}

Expand Down Expand Up @@ -192,31 +169,35 @@ func (b *bombardier) recordRps() {
}

func (b *bombardier) bombard() {
fmt.Printf("Bombarding %v with %v requests using %v connections\n",
b.url, b.numReqs, b.numConns)
b.bar = pb.StartNew(b.numReqs)
b.printIntro()
b.bar.Start()
bombardmentBegin := time.Now()
b.start = time.Now()
for i := 0; i < b.numConns; i++ {
var headers *fasthttp.RequestHeader
if b.requestHeaders != nil {
headers = new(fasthttp.RequestHeader)
b.requestHeaders.CopyTo(headers)
}
go b.Worker(headers)
for i := uint64(0); i < b.conf.numConns; i++ {
go b.Worker()
}
go b.rateMeter()
b.jobs.Wait()
b.barrier.wait()
b.timeTaken = time.Since(bombardmentBegin)
b.done <- true
<-b.done
b.bar.Finish()
b.bar.FinishPrint("Done!")
}

func (b *bombardier) throughput() float64 {
return float64(b.bytesWritten) / b.timeTaken.Seconds()
}

func (b *bombardier) printIntro() {
if b.conf.testType == counted {
fmt.Printf("Bombarding %v with %v requests using %v connections\n",
b.conf.url, *b.conf.numReqs, b.conf.numConns)
} else if b.conf.testType == timed {
fmt.Printf("Bombarding %v for %v using %v connections\n",
b.conf.url, *b.conf.duration, b.conf.numConns)
}
}

func (b *bombardier) printLatencyStats() {
percentiles := []float64{50.0, 75.0, 90.0, 99.0}
fmt.Println(" Latency Distribution")
Expand All @@ -242,14 +223,21 @@ func (b *bombardier) printStats() {
fmt.Printf(" %-10v %10v/s\n", "Throughput:", formatBinary(b.throughput()))
}

var headers = new(headersList)
var numConns = flag.Int("c", 200, "Maximum number of concurrent connections")
var numReqs = flag.Int("n", 10000, "Number of requests")
var timeout = flag.Duration("timeout", 2*time.Second, "Socket/request timeout")
var latencies = flag.Bool("latencies", false, "Print latency statistics")
var (
numReqs = new(nullableUint64)
duration = new(nullableDuration)
headers = new(headersList)
numConns = flag.Uint64("c", 200, "Maximum number of concurrent connections")
timeout = flag.Duration("timeout", 2*time.Second, "Socket/request timeout")
latencies = flag.Bool("latencies", false, "Print latency statistics")
method = flag.String("m", "GET", "Request method")
body = flag.String("data", "", "Request body")
)

func main() {
flag.Var(headers, "H", "HTTP headers to use")
flag.Var(numReqs, "n", "Number of requests")
flag.Var(duration, "d", "Duration of test")
flag.Parse()
if flag.NArg() == 0 {
fmt.Println("No URL supplied")
Expand All @@ -260,20 +248,17 @@ func main() {
fmt.Println("Too many arguments are supplied")
os.Exit(1)
}
rawurl := flag.Args()[0]
url, err := url.ParseRequestURI(rawurl)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if url.Host == "" || (url.Scheme != "http" && url.Scheme != "https") {
fmt.Println("No hostname or invalid scheme")
os.Exit(1)
}
bombardier, err := newBombardier(
*numConns, *numReqs,
url.String(), headers,
*timeout)
config{
numConns: *numConns,
numReqs: numReqs.val,
duration: duration.val,
url: flag.Arg(0),
headers: headers,
timeout: *timeout,
method: *method,
body: *body,
})
if err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down
Loading

0 comments on commit 45f56ed

Please sign in to comment.