Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions args_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type kingpinParser struct {

printSpec *nullableString
noPrint bool
detailed bool

formatSpec string
}
Expand All @@ -63,6 +64,7 @@ func newKingpinParser() argsParser {
clientType: fhttp,
printSpec: new(nullableString),
noPrint: false,
detailed: false,
formatSpec: "plain-text",
}

Expand Down Expand Up @@ -164,6 +166,9 @@ func newKingpinParser() argsParser {
Short('q').
BoolVar(&kparser.noPrint)

app.Flag("detailed", "Print detailed statistics").
BoolVar(&kparser.detailed)

app.Flag("format", "Which format to use to output the result. "+
"<spec> is either a name (or its shorthand) of some format "+
"understood by bombardier or a path to the user-defined template, "+
Expand Down Expand Up @@ -231,6 +236,7 @@ func (k *kingpinParser) parse(args []string) (config, error) {
printIntro: pi,
printProgress: pp,
printResult: pr,
printDetailed: k.detailed,
format: format,
}, nil
}
Expand Down
95 changes: 63 additions & 32 deletions bombardier.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"io/ioutil"
"os"
"os/signal"
"sort"
"strings"
"sync"
"sync/atomic"
"text/template"
"time"

Expand All @@ -24,12 +24,8 @@ type bombardier struct {
bytesRead, bytesWritten int64

// HTTP codes
req1xx uint64
req2xx uint64
req3xx uint64
req4xx uint64
req5xx uint64
others uint64
statsMu sync.Mutex
statusCodes map[int]uint64

conf config
barrier completionBarrier
Expand Down Expand Up @@ -67,6 +63,7 @@ func newBombardier(c config) (*bombardier, error) {
b.conf = c
b.latencies = uhist.Default()
b.requests = fhist.Default()
b.statusCodes = make(map[int]uint64)

if b.conf.testType() == counted {
b.bar = pb.New64(int64(*b.conf.numReqs))
Expand Down Expand Up @@ -196,8 +193,12 @@ func (b *bombardier) prepareTemplate() (*template.Template, error) {
"WithLatencies": func() bool {
return b.conf.printLatencies
},
"FormatBinary": formatBinary,
"FormatTimeUs": formatTimeUs,
"WithDetailedStats": func() bool {
return b.conf.printDetailed
},
"SortedStatusCodes": sortedStatusCodes,
"FormatBinary": formatBinary,
"FormatTimeUs": formatTimeUs,
"FormatTimeUsUint64": func(us uint64) string {
return formatTimeUs(float64(us))
},
Expand Down Expand Up @@ -230,22 +231,10 @@ func (b *bombardier) writeStatistics(
b.rpl.Lock()
b.reqs++
b.rpl.Unlock()
var counter *uint64
switch code / 100 {
case 1:
counter = &b.req1xx
case 2:
counter = &b.req2xx
case 3:
counter = &b.req3xx
case 4:
counter = &b.req4xx
case 5:
counter = &b.req5xx
default:
counter = &b.others
}
atomic.AddUint64(counter, 1)

b.statsMu.Lock()
b.statusCodes[code]++
b.statsMu.Unlock()
}

func (b *bombardier) performSingleRequest() {
Expand Down Expand Up @@ -381,13 +370,6 @@ func (b *bombardier) gatherInfo() internal.TestInfo {
BytesWritten: b.bytesWritten,
TimeTaken: b.timeTaken,

Req1XX: b.req1xx,
Req2XX: b.req2xx,
Req3XX: b.req3xx,
Req4XX: b.req4xx,
Req5XX: b.req5xx,
Others: b.others,

Latencies: b.latencies,
Requests: b.requests,
},
Expand All @@ -401,6 +383,37 @@ func (b *bombardier) gatherInfo() internal.TestInfo {
info.Spec.NumberOfRequests = *b.conf.numReqs
}

sc := make(map[int]uint64, len(b.statusCodes))
var req1xx, req2xx, req3xx, req4xx, req5xx, others uint64

b.statsMu.Lock()
for k, v := range b.statusCodes {
sc[k] = v
switch k / 100 {
case 1:
req1xx += v
case 2:
req2xx += v
case 3:
req3xx += v
case 4:
req4xx += v
case 5:
req5xx += v
default:
others += v
}
}
b.statsMu.Unlock()

info.Result.Req1XX = req1xx
info.Result.Req2XX = req2xx
info.Result.Req3XX = req3xx
info.Result.Req4XX = req4xx
info.Result.Req5XX = req5xx
info.Result.Others = others
info.Result.StatusCodes = sc

if b.conf.headers != nil {
for _, h := range *b.conf.headers {
info.Spec.Headers = append(info.Spec.Headers,
Expand Down Expand Up @@ -440,6 +453,24 @@ func (b *bombardier) disableOutput() {
b.bar.NotPrint = true
}

type statusCodePair struct {
Code int
Count uint64
}

func sortedStatusCodes(m map[int]uint64) []statusCodePair {
keys := make([]int, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
res := make([]statusCodePair, len(keys))
for i, k := range keys {
res[i] = statusCodePair{Code: k, Count: m[k]}
}
return res
}

func main() {
cfg, err := parser.parse(os.Args)
if err != nil {
Expand Down
25 changes: 15 additions & 10 deletions bombardier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,17 @@ func testBombardierHTTPCodeRecording(clientType clientTyp, t *testing.T) {
}
b.disableOutput()
b.bombard()
info := b.gatherInfo()
expectation := []struct {
name string
reqsGot uint64
expected uint64
}{
{"errored", b.others, eachCodeCount * 2},
{"2xx", b.req2xx, eachCodeCount},
{"3xx", b.req3xx, eachCodeCount},
{"4xx", b.req4xx, eachCodeCount},
{"5xx", b.req5xx, eachCodeCount},
{"errored", info.Result.Others, eachCodeCount * 2},
{"2xx", info.Result.Req2XX, eachCodeCount},
{"3xx", info.Result.Req3XX, eachCodeCount},
{"4xx", info.Result.Req4XX, eachCodeCount},
{"5xx", info.Result.Req5XX, eachCodeCount},
}
for _, e := range expectation {
if e.reqsGot != e.expected {
Expand Down Expand Up @@ -421,8 +422,11 @@ func testBombardierClientCerts(clientType clientTyp, t *testing.T) {
b.disableOutput()

b.bombard()
if b.req2xx != 1 {
t.Error("no 2xx responses, total =", b.reqs, ", 1xx/2xx/3xx/4xx/5xx =", b.req1xx, b.req2xx, b.req3xx, b.req4xx, b.req5xx)
info := b.gatherInfo()
if info.Result.Req2XX != 1 {
t.Error("no 2xx responses, total =", b.reqs, ", 1xx/2xx/3xx/4xx/5xx =",
info.Result.Req1XX, info.Result.Req2XX, info.Result.Req3XX,
info.Result.Req4XX, info.Result.Req5XX)
}

server.Close()
Expand Down Expand Up @@ -464,9 +468,10 @@ func testBombardierRateLimiting(clientType clientTyp, t *testing.T) {
}
b.disableOutput()
b.bombard()
if float64(b.req2xx) < float64(rate)*0.75 ||
float64(b.req2xx) > float64(rate)*1.25 {
t.Error(rate, b.req2xx)
info := b.gatherInfo()
if float64(info.Result.Req2XX) < float64(rate)*0.75 ||
float64(info.Result.Req2XX) > float64(rate)*1.25 {
t.Error(rate, info.Result.Req2XX)
}
}

Expand Down
2 changes: 1 addition & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type config struct {
rate *uint64
clientType clientTyp

printIntro, printProgress, printResult bool
printIntro, printProgress, printResult, printDetailed bool

format format
}
Expand Down
2 changes: 2 additions & 0 deletions internal/test_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type Results struct {
Req1XX, Req2XX, Req3XX, Req4XX, Req5XX uint64
Others uint64

StatusCodes map[int]uint64

Errors []ErrorWithCount

Latencies ReadonlyUint64Histogram
Expand Down
13 changes: 13 additions & 0 deletions templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ const (
{{ " HTTP codes:" }}
{{ printf " 1xx - %v, 2xx - %v, 3xx - %v, 4xx - %v, 5xx - %v" .Req1XX .Req2XX .Req3XX .Req4XX .Req5XX }}
{{- printf "\n others - %v" .Others }}
{{- if WithDetailedStats }}
{{- "\n Detailed status codes:" }}
{{- range $pair := SortedStatusCodes .StatusCodes }}
{{- printf "\n %d - %d" $pair.Code $pair.Count }}
{{- end -}}
{{- end }}
{{- with .Errors }}
{{- "\n Errors:"}}
{{- range . }}
Expand Down Expand Up @@ -129,6 +135,13 @@ const (
,"req5xx":{{ .Req5XX -}}
,"others":{{ .Others -}}

,"statusCodes":{
{{- range $index, $pair := SortedStatusCodes .StatusCodes -}}
{{- if ne $index 0 -}},{{- end -}}
{{- printf "\"%d\":%d" $pair.Code $pair.Count -}}
{{- end -}}
}

{{- with .Errors -}}
,"errors":[
{{- range $index, $error := . -}}
Expand Down