Full Code of codesenberg/bombardier for AI

master 2d495aca5b23 cached
50 files
143.3 KB
50.1k tokens
258 symbols
1 requests
Download .txt
Repository: codesenberg/bombardier
Branch: master
Commit: 2d495aca5b23
Files: 50
Total size: 143.3 KB

Directory structure:
gitextract_k5a5acgj/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .semaphore/
│   └── semaphore.yml
├── LICENSE
├── README.md
├── args_parser.go
├── args_parser_test.go
├── bombardier.go
├── bombardier_performance_test.go
├── bombardier_test.go
├── build.py
├── client_cert.go
├── client_cert_test.go
├── clients.go
├── clients_test.go
├── cmd/
│   └── utils/
│       └── simplebenchserver/
│           ├── doc.go
│           └── main.go
├── common.go
├── completion_barriers.go
├── completion_barriers_test.go
├── config.go
├── config_test.go
├── dialer.go
├── doc.go
├── docs/
│   └── CONTRIBUTING.md
├── error_map.go
├── error_map_test.go
├── flags.go
├── flags_test.go
├── format.go
├── format_test.go
├── go.mod
├── go.sum
├── headers.go
├── headers_test.go
├── internal/
│   └── test_info.go
├── limiter.go
├── limiter_barrier_test.go
├── limiter_test.go
├── proxy_reader.go
├── rateestimator.go
├── rateestimator_test.go
├── template/
│   └── doc.go
├── templates.go
├── testbody.txt
├── testclient.cert
├── testclient.key
├── testserver.cert
└── testserver.key

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
This is an example of what a **bug report** can look like. Please, feel free to also provide any other information relevant to the issue.

### What version of bombardier are you using?
Hash of the commit, like 

[00d7965d6cae34c62042abb0f6c45c45b870dcf3](https://github.com/codesenberg/bombardier/commit/00d7965d6cae34c62042abb0f6c45c45b870dcf3)

in case you've built _bombardier_ yourself or version obtained by
```
bombardier --version
```
in case you are using binaries.

### What operating system and processor architecture are you using (if relevant)?
Examples are `windows/amd64`, `linux/amd64`, `darwin/amd64`, etc.

### What did you do?

Describe steps that can be used to reproduce the error.

### What you expected to happen?

### What actually happened?


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Before submitting a pull request be sure to check the code with [gometalinter](https://github.com/alecthomas/gometalinter).
This can save a considerable amount of time during code review.

Generally, try to follow this format when writing commit messages:
```
<name of the subsystem of bombardier if applicable or "all">: <short description of changes>

<A more elaborate description of the changes and maybe some explanations go here.>

<Fixes #<number of issue>, updates #<number of issue>, closes #<number of issue>. If applicable.>
```

Examples of such commit messages can be found in the commit log of this project or 
[in this section](https://golang.org/doc/contribute.html#commit_changes) of Go's Contribution Guidelines,
from which this format was adopted.

The pull request itself can contain a short description of changes made, questions or provide some other information, etc.


================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof
cover.*
bombardier
bombardier-*

================================================
FILE: .semaphore/semaphore.yml
================================================
version: v1.0
name: codesenberg/bombardier
agent:
  machine:
    type: e1-standard-2
    os_image: ubuntu2004
blocks:
  - name: Test
    task:
      prologue:
        commands:
          - checkout
          - go install gotest.tools/gotestsum@latest
      jobs:
        - name: Test go 1.21
          commands:
            - sem-version go 1.21
            - gotestsum --junitfile report.xml ./...
        - name: Test go 1.22
          commands:
            - sem-version go 1.22
            - gotestsum --junitfile report.xml ./...
      epilogue:
        always:
          commands:
            - '[[ -f report.xml ]] && test-results publish report.xml'
after_pipeline:
  task:
    jobs:
      - name: Publish test results
        commands:
          - test-results gen-pipeline-report


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

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, 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:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# bombardier [![Build Status](https://codesenberg.semaphoreci.com/badges/bombardier/branches/master.svg?key=249c678c-eb2a-441e-8128-1bdcfb9aaca6)](https://codesenberg.semaphoreci.com/projects/bombardier) [![Go Report Card](https://goreportcard.com/badge/github.com/codesenberg/bombardier)](https://goreportcard.com/report/github.com/codesenberg/bombardier) [![GoDoc](https://godoc.org/github.com/codesenberg/bombardier?status.svg)](http://godoc.org/github.com/codesenberg/bombardier)
![Logo](https://raw.githubusercontent.com/codesenberg/bombardier/master/img/logo.png)
bombardier is a HTTP(S) benchmarking tool. It is written in Go programming language and uses excellent [fasthttp](https://github.com/valyala/fasthttp) instead of Go's default http library, because of its lightning fast performance. 

With `bombardier v1.1` and higher you can now use `net/http` client if you need to test HTTP/2.x services or want to use a more RFC-compliant HTTP client.

## Installation
You can grab binaries in the [releases](https://github.com/codesenberg/bombardier/releases) section.
Alternatively, to get latest and greatest run:

Go 1.18+: `go install github.com/codesenberg/bombardier@latest`

## Usage
```
bombardier [<flags>] <url>
```

For a more detailed information about flags consult [GoDoc](http://godoc.org/github.com/codesenberg/bombardier).

## Known issues
AFAIK, it's impossible to pass Host header correctly with `fasthttp`, you can use `net/http`(`--http1`/`--http2` flags) to workaround this issue.

## Examples
Example of running `bombardier` against [this server](https://godoc.org/github.com/codesenberg/bombardier/cmd/utils/simplebenchserver):
```
> bombardier -c 125 -n 10000000 http://localhost:8080
Bombarding http://localhost:8080 with 10000000 requests using 125 connections
 10000000 / 10000000 [============================================] 100.00% 37s Done!
Statistics        Avg      Stdev        Max
  Reqs/sec    264560.00   10733.06     268434
  Latency      471.00us   522.34us    51.00ms
  HTTP codes:
    1xx - 0, 2xx - 10000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:   292.92MB/s
```
Or, against a realworld server(with latency distribution):
```
> bombardier -c 200 -d 10s -l http://ya.ru
Bombarding http://ya.ru for 10s using 200 connections
[=========================================================================] 10s Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      6607.00     524.56       7109
  Latency       29.86ms     5.36ms   305.02ms
  Latency Distribution
     50%    28.00ms
     75%    32.00ms
     90%    34.00ms
     99%    48.00ms
  HTTP codes:
    1xx - 0, 2xx - 0, 3xx - 66561, 4xx - 0, 5xx - 0
    others - 5
  Errors:
    dialing to the given TCP address timed out - 5
  Throughput:     3.06MB/s
```


================================================
FILE: args_parser.go
================================================
package main

import (
	"fmt"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/alecthomas/kingpin"
	"github.com/goware/urlx"
)

type argsParser interface {
	parse([]string) (config, error)
}

type kingpinParser struct {
	app *kingpin.Application

	url string

	numReqs           *nullableUint64
	duration          *nullableDuration
	headers           *headersList
	numConns          uint64
	timeout           time.Duration
	latencies         bool
	insecure          bool
	disableKeepAlives bool
	method            string
	body              string
	bodyFilePath      string
	stream            bool
	certPath          string
	keyPath           string
	rate              *nullableUint64
	clientType        clientTyp

	printSpec *nullableString
	noPrint   bool

	formatSpec string
}

func newKingpinParser() argsParser {
	kparser := &kingpinParser{
		numReqs:      new(nullableUint64),
		duration:     new(nullableDuration),
		headers:      new(headersList),
		numConns:     defaultNumberOfConns,
		timeout:      defaultTimeout,
		latencies:    false,
		method:       "GET",
		body:         "",
		bodyFilePath: "",
		stream:       false,
		certPath:     "",
		keyPath:      "",
		insecure:     false,
		url:          "",
		rate:         new(nullableUint64),
		clientType:   fhttp,
		printSpec:    new(nullableString),
		noPrint:      false,
		formatSpec:   "plain-text",
	}

	app := kingpin.New("", "Fast cross-platform HTTP benchmarking tool").
		Version("bombardier version " + version + " " + runtime.GOOS + "/" +
			runtime.GOARCH)
	app.Flag("connections", "Maximum number of concurrent connections").
		Short('c').
		PlaceHolder(strconv.FormatUint(defaultNumberOfConns, decBase)).
		Uint64Var(&kparser.numConns)
	app.Flag("timeout", "Socket/request timeout").
		PlaceHolder(defaultTimeout.String()).
		Short('t').
		DurationVar(&kparser.timeout)
	app.Flag("latencies", "Print latency statistics").
		Short('l').
		BoolVar(&kparser.latencies)
	app.Flag("method", "Request method").
		PlaceHolder("GET").
		Short('m').
		StringVar(&kparser.method)
	app.Flag("body", "Request body").
		Default("").
		Short('b').
		StringVar(&kparser.body)
	app.Flag("body-file", "File to use as request body").
		Default("").
		Short('f').
		StringVar(&kparser.bodyFilePath)
	app.Flag("stream", "Specify whether to stream body using "+
		"chunked transfer encoding or to serve it from memory").
		Short('s').
		BoolVar(&kparser.stream)
	app.Flag("cert", "Path to the client's TLS Certificate").
		Default("").
		StringVar(&kparser.certPath)
	app.Flag("key", "Path to the client's TLS Certificate Private Key").
		Default("").
		StringVar(&kparser.keyPath)
	app.Flag("insecure",
		"Controls whether a client verifies the server's certificate"+
			" chain and host name").
		Short('k').
		BoolVar(&kparser.insecure)
	app.Flag("disableKeepAlives",
		"Disable HTTP keep-alive. For fasthttp use -H 'Connection: close'").
		Short('a').
		BoolVar(&kparser.disableKeepAlives)

	app.Flag("header", "HTTP headers to use(can be repeated)").
		PlaceHolder("\"K: V\"").
		Short('H').
		SetValue(kparser.headers)
	app.Flag("requests", "Number of requests").
		PlaceHolder("[pos. int.]").
		Short('n').
		SetValue(kparser.numReqs)
	app.Flag("duration", "Duration of test").
		PlaceHolder(defaultTestDuration.String()).
		Short('d').
		SetValue(kparser.duration)

	app.Flag("rate", "Rate limit in requests per second").
		PlaceHolder("[pos. int.]").
		Short('r').
		SetValue(kparser.rate)

	app.Flag("fasthttp", "Use fasthttp client").
		Action(func(*kingpin.ParseContext) error {
			kparser.clientType = fhttp
			return nil
		}).
		Bool()
	app.Flag("http1", "Use net/http client with forced HTTP/1.x").
		Action(func(*kingpin.ParseContext) error {
			kparser.clientType = nhttp1
			return nil
		}).
		Bool()
	app.Flag("http2", "Use net/http client with enabled HTTP/2.0").
		Action(func(*kingpin.ParseContext) error {
			kparser.clientType = nhttp2
			return nil
		}).
		Bool()

	app.Flag(
		"print", "Specifies what to output. Comma-separated list of values"+
			" 'intro' (short: 'i'), 'progress' (short: 'p'),"+
			" 'result' (short: 'r'). Examples:"+
			"\n\t* i,p,r (prints everything)"+
			"\n\t* intro,result (intro & result)"+
			"\n\t* r (result only)"+
			"\n\t* result (same as above)").
		PlaceHolder("<spec>").
		Short('p').
		SetValue(kparser.printSpec)
	app.Flag("no-print", "Don't output anything").
		Short('q').
		BoolVar(&kparser.noPrint)

	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, "+
		"which uses Go's text/template syntax, prefixed with 'path:' string "+
		"(without single quotes), i.e. \"path:/some/path/to/your.template\" "+
		" or \"path:C:\\some\\path\\to\\your.template\" in case of Windows. "+
		"Formats understood by bombardier are:"+
		"\n\t* plain-text (short: pt)"+
		"\n\t* json (short: j)").
		PlaceHolder("<spec>").
		Short('o').
		StringVar(&kparser.formatSpec)

	app.Arg("url", "Target's URL").Required().
		StringVar(&kparser.url)

	kparser.app = app
	return argsParser(kparser)
}

func (k *kingpinParser) parse(args []string) (config, error) {
	k.app.Name = args[0]
	_, err := k.app.Parse(args[1:])
	if err != nil {
		return emptyConf, err
	}
	pi, pp, pr := true, true, true
	if k.printSpec.val != nil {
		pi, pp, pr, err = parsePrintSpec(*k.printSpec.val)
		if err != nil {
			return emptyConf, err
		}
	}
	if k.noPrint {
		pi, pp, pr = false, false, false
	}
	format := formatFromString(k.formatSpec)
	if format == nil {
		return emptyConf, fmt.Errorf(
			"unknown format or invalid format spec %q", k.formatSpec,
		)
	}
	url, err := urlx.Parse(k.url)
	if err != nil {
		return emptyConf, err
	}
	return config{
		numConns:          k.numConns,
		numReqs:           k.numReqs.val,
		duration:          k.duration.val,
		url:               url,
		headers:           k.headers,
		timeout:           k.timeout,
		method:            k.method,
		body:              k.body,
		bodyFilePath:      k.bodyFilePath,
		stream:            k.stream,
		keyPath:           k.keyPath,
		certPath:          k.certPath,
		printLatencies:    k.latencies,
		insecure:          k.insecure,
		disableKeepAlives: k.disableKeepAlives,
		rate:              k.rate.val,
		clientType:        k.clientType,
		printIntro:        pi,
		printProgress:     pp,
		printResult:       pr,
		format:            format,
	}, nil
}

func parsePrintSpec(spec string) (bool, bool, bool, error) {
	pi, pp, pr := false, false, false
	if spec == "" {
		return false, false, false, errEmptyPrintSpec
	}
	parts := strings.Split(spec, ",")
	partsCount := 0
	for _, p := range parts {
		switch p {
		case "i", "intro":
			pi = true
		case "p", "progress":
			pp = true
		case "r", "result":
			pr = true
		default:
			return false, false, false,
				fmt.Errorf("%q is not a valid part of print spec", p)
		}
		partsCount++
	}
	if partsCount < 1 || partsCount > 3 {
		return false, false, false,
			fmt.Errorf(
				"spec %q has too many parts, at most 3 are allowed", spec,
			)
	}
	return pi, pp, pr, nil
}


================================================
FILE: args_parser_test.go
================================================
package main

import (
	"fmt"
	"reflect"
	"strconv"
	"testing"
	"time"
)

const (
	programName = "bombardier"
)

func TestInvalidArgsParsing(t *testing.T) {
	expectations := []struct {
		in  []string
		out string
	}{
		{
			[]string{programName},
			"required argument 'url' not provided",
		},
		{
			[]string{programName, "http://google.com", "http://yahoo.com"},
			"unexpected http://yahoo.com",
		},
	}
	for _, e := range expectations {
		p := newKingpinParser()
		if _, err := p.parse(e.in); err == nil ||
			err.Error() != e.out {
			t.Error(err, e.out)
		}
	}
}

func TestUnspecifiedArgParsing(t *testing.T) {
	p := newKingpinParser()
	args := []string{programName, "--someunspecifiedflag"}
	_, err := p.parse(args)
	if err == nil {
		t.Fail()
	}
}

func TestArgsParsing(t *testing.T) {
	ten := uint64(10)
	expectations := []struct {
		in  [][]string
		out config
	}{
		{
			[][]string{
				{programName, "localhost:8080"},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("http://localhost:8080"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{programName, "https://localhost"},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://localhost"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{{programName, "https://somehost.somedomain"}},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"-c", "10",
					"-n", strconv.FormatUint(defaultNumberOfReqs, decBase),
					"-t", "10s",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-c10",
					"-n" + strconv.FormatUint(defaultNumberOfReqs, decBase),
					"-t10s",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--connections", "10",
					"--requests", strconv.FormatUint(defaultNumberOfReqs, decBase),
					"--timeout", "10s",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--connections=10",
					"--requests=" + strconv.FormatUint(defaultNumberOfReqs, decBase),
					"--timeout=10s",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      10,
				timeout:       10 * time.Second,
				headers:       new(headersList),
				method:        "GET",
				numReqs:       &defaultNumberOfReqs,
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--latencies",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-l",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:       defaultNumberOfConns,
				timeout:        defaultTimeout,
				headers:        new(headersList),
				printLatencies: true,
				method:         "GET",
				url:            ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:     true,
				printProgress:  true,
				printResult:    true,
				format:         knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--insecure",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-k",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				insecure:      true,
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--key", "testclient.key",
					"--cert", "testclient.cert",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--key=testclient.key",
					"--cert=testclient.cert",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				keyPath:       "testclient.key",
				certPath:      "testclient.cert",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--method", "POST",
					"--body", "reqbody",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--method=POST",
					"--body=reqbody",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-m", "POST",
					"-b", "reqbody",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-mPOST",
					"-breqbody",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "POST",
				body:          "reqbody",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--header", "One: Value one",
					"--header", "Two: Value two",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-H", "One: Value one",
					"-H", "Two: Value two",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--header=One: Value one",
					"--header=Two: Value two",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns: defaultNumberOfConns,
				timeout:  defaultTimeout,
				headers: &headersList{
					{"One", "Value one"},
					{"Two", "Value two"},
				},
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--rate", "10",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-r", "10",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--rate=10",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-r10",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				rate:          &ten,
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--fasthttp",
					"https://somehost.somedomain",
				},
				{
					programName,
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				clientType:    fhttp,
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--http1",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				clientType:    nhttp1,
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--http2",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				clientType:    nhttp2,
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--body-file=testbody.txt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--body-file", "testbody.txt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-f", "testbody.txt",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				bodyFilePath:  "testbody.txt",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--stream",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-s",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				stream:        true,
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--print=r,i,p",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print", "r,i,p",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-p", "r,i,p",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print=result,i,p",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print", "r,intro,p",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-p", "r,i,progress",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--print=i,r",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print", "i,r",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-p", "i,r",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print=intro,r",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--print", "i,result",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-p", "intro,r",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: false,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--no-print",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-q",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    false,
				printProgress: false,
				printResult:   false,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--format", "plain-text",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format", "pt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format=plain-text",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format=pt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-o", "plain-text",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-o", "pt",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("plain-text"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--format", "json",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format", "j",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format=json",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format=j",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-o", "json",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-o", "j",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        knownFormat("json"),
			},
		},
		{
			[][]string{
				{
					programName,
					"--format", "path:/path/to/tmpl.txt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"--format=path:/path/to/tmpl.txt",
					"https://somehost.somedomain",
				},
				{
					programName,
					"-o", "path:/path/to/tmpl.txt",
					"https://somehost.somedomain",
				},
			},
			config{
				numConns:      defaultNumberOfConns,
				timeout:       defaultTimeout,
				headers:       new(headersList),
				method:        "GET",
				url:           ParseURLOrPanic("https://somehost.somedomain"),
				printIntro:    true,
				printProgress: true,
				printResult:   true,
				format:        userDefinedTemplate("/path/to/tmpl.txt"),
			},
		},
	}
	for _, e := range expectations {
		for _, args := range e.in {
			p := newKingpinParser()
			cfg, err := p.parse(args)
			if err != nil {
				t.Error(err)
				continue
			}
			if !reflect.DeepEqual(cfg, e.out) {
				t.Logf("Expected: %#v", e.out)
				t.Logf("Got:      %#v", cfg)
				t.Fail()
			}
		}
	}
}

func TestParsePrintSpec(t *testing.T) {
	exps := []struct {
		spec    string
		results [3]bool
		err     error
	}{
		{
			"",
			[3]bool{},
			errEmptyPrintSpec,
		},
		{
			"a,b,c",
			[3]bool{},
			fmt.Errorf("%q is not a valid part of print spec", "a"),
		},
		{
			"i,p,r,i",
			[3]bool{},
			fmt.Errorf(
				"spec %q has too many parts, at most 3 are allowed", "i,p,r,i",
			),
		},
		{
			"i",
			[3]bool{true, false, false},
			nil,
		},
		{
			"p",
			[3]bool{false, true, false},
			nil,
		},
		{
			"r",
			[3]bool{false, false, true},
			nil,
		},
		{
			"i,p,r",
			[3]bool{true, true, true},
			nil,
		},
	}
	for _, e := range exps {
		var (
			act = [3]bool{}
			err error
		)
		act[0], act[1], act[2], err = parsePrintSpec(e.spec)
		if !reflect.DeepEqual(err, e.err) {
			t.Errorf("For %q, expected err = %q, but got %q",
				e.spec, e.err, err,
			)
			continue
		}
		if !reflect.DeepEqual(e.results, act) {
			t.Errorf("For %q, expected result = %+v, but got %+v",
				e.spec, e.results, act,
			)
		}
	}
}

func TestArgsParsingWithEmptyPrintSpec(t *testing.T) {
	p := newKingpinParser()
	c, err := p.parse(
		[]string{programName, "--print=", "somehost.somedomain"})
	if err == nil {
		t.Fail()
	}
	if c != emptyConf {
		t.Fail()
	}
}

func TestArgsParsingWithInvalidPrintSpec(t *testing.T) {
	invalidSpecs := [][]string{
		{programName, "--format", "noprefix.txt", "somehost.somedomain"},
		{programName, "--format=noprefix.txt", "somehost.somedomain"},
		{programName, "-o", "noprefix.txt", "somehost.somedomain"},
		{programName, "--format", "unknown-format", "somehost.somedomain"},
		{programName, "--format=unknown-format", "somehost.somedomain"},
		{programName, "-o", "unknown-format", "somehost.somedomain"},
	}
	p := newKingpinParser()
	for _, is := range invalidSpecs {
		c, err := p.parse(is)
		if err == nil || c != emptyConf {
			t.Errorf("invalid print spec %q parsed correctly", is)
		}
	}
}

func TestEmbeddedURLParsing(t *testing.T) {
	p := newKingpinParser()
	url := "http://127.0.0.1:8080/to?url=http://10.100.99.41:38667"
	c, err := p.parse([]string{programName, url})
	if err != nil {
		t.Error(err)
	}
	if c.url.String() != url {
		t.Errorf("got %q, wanted %q", c.url, url)
	}
}


================================================
FILE: bombardier.go
================================================
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"
	"sync"
	"sync/atomic"
	"text/template"
	"time"

	"github.com/codesenberg/bombardier/internal"

	"github.com/cheggaaa/pb"
	fhist "github.com/codesenberg/concurrent/float64/histogram"
	uhist "github.com/codesenberg/concurrent/uint64/histogram"
	uuid "github.com/satori/go.uuid"
)

type bombardier struct {
	bytesRead, bytesWritten int64

	// HTTP codes
	req1xx uint64
	req2xx uint64
	req3xx uint64
	req4xx uint64
	req5xx uint64
	others uint64

	conf        config
	barrier     completionBarrier
	ratelimiter limiter
	wg          sync.WaitGroup

	timeTaken time.Duration
	latencies *uhist.Histogram
	requests  *fhist.Histogram

	client   client
	doneChan chan struct{}

	// RPS metrics
	rpl   sync.Mutex
	reqs  int64
	start time.Time

	// Errors
	errors *errorMap

	// Progress bar
	bar *pb.ProgressBar

	// Output
	out      io.Writer
	template *template.Template
}

func newBombardier(c config) (*bombardier, error) {
	if err := c.checkArgs(); err != nil {
		return nil, err
	}
	b := new(bombardier)
	b.conf = c
	b.latencies = uhist.Default()
	b.requests = fhist.Default()

	if b.conf.testType() == counted {
		b.bar = pb.New64(int64(*b.conf.numReqs))
		b.bar.ShowSpeed = true
	} else if b.conf.testType() == timed {
		b.bar = pb.New64(b.conf.duration.Nanoseconds() / 1e9)
		b.bar.ShowCounters = false
		b.bar.ShowPercent = false
	}
	b.bar.ManualUpdate = true

	if b.conf.testType() == counted {
		b.barrier = newCountingCompletionBarrier(*b.conf.numReqs)
	} else {
		b.barrier = newTimedCompletionBarrier(*b.conf.duration)
	}

	if b.conf.rate != nil {
		b.ratelimiter = newBucketLimiter(*b.conf.rate)
	} else {
		b.ratelimiter = &nooplimiter{}
	}

	b.out = os.Stdout

	tlsConfig, err := generateTLSConfig(c)
	if err != nil {
		return nil, err
	}

	var (
		pbody *string
		bsp   bodyStreamProducer
	)
	if c.stream {
		if c.bodyFilePath != "" {
			bsp = func() (io.ReadCloser, error) {
				return os.Open(c.bodyFilePath)
			}
		} else {
			bsp = func() (io.ReadCloser, error) {
				return ioutil.NopCloser(
					proxyReader{strings.NewReader(c.body)},
				), nil
			}
		}
	} else {
		pbody = &c.body
		if c.bodyFilePath != "" {
			var bodyBytes []byte
			bodyBytes, err = ioutil.ReadFile(c.bodyFilePath)
			if err != nil {
				return nil, err
			}
			sbody := string(bodyBytes)
			pbody = &sbody
		}
	}

	cc := &clientOpts{
		HTTP2:             false,
		maxConns:          c.numConns,
		timeout:           c.timeout,
		tlsConfig:         tlsConfig,
		disableKeepAlives: c.disableKeepAlives,

		headers:      c.headers,
		requestURL:   c.url,
		method:       c.method,
		body:         pbody,
		bodProd:      bsp,
		bytesRead:    &b.bytesRead,
		bytesWritten: &b.bytesWritten,
	}
	b.client = makeHTTPClient(c.clientType, cc)

	if !b.conf.printProgress {
		b.bar.Output = ioutil.Discard
		b.bar.NotPrint = true
	}

	b.template, err = b.prepareTemplate()
	if err != nil {
		return nil, err
	}

	b.wg.Add(int(c.numConns))
	b.errors = newErrorMap()
	b.doneChan = make(chan struct{}, 2)
	return b, nil
}

func makeHTTPClient(clientType clientTyp, cc *clientOpts) client {
	var cl client
	switch clientType {
	case nhttp1:
		cl = newHTTPClient(cc)
	case nhttp2:
		cc.HTTP2 = true
		cl = newHTTPClient(cc)
	case fhttp:
		fallthrough
	default:
		cl = newFastHTTPClient(cc)
	}
	return cl
}

func (b *bombardier) prepareTemplate() (*template.Template, error) {
	var (
		templateBytes []byte
		err           error
	)
	switch f := b.conf.format.(type) {
	case knownFormat:
		templateBytes = f.template()
	case userDefinedTemplate:
		templateBytes, err = ioutil.ReadFile(string(f))
		if err != nil {
			return nil, err
		}
	default:
		panic("format can't be nil at this point, this is a bug")
	}
	outputTemplate, err := template.New("output-template").
		Funcs(template.FuncMap{
			"WithLatencies": func() bool {
				return b.conf.printLatencies
			},
			"FormatBinary": formatBinary,
			"FormatTimeUs": formatTimeUs,
			"FormatTimeUsUint64": func(us uint64) string {
				return formatTimeUs(float64(us))
			},
			"FloatsToArray": func(ps ...float64) []float64 {
				return ps
			},
			"Multiply": func(num, coeff float64) float64 {
				return num * coeff
			},
			"StringToBytes": func(s string) []byte {
				return []byte(s)
			},
			"UUIDV1": uuid.NewV1,
			"UUIDV2": uuid.NewV2,
			"UUIDV3": uuid.NewV3,
			"UUIDV4": uuid.NewV4,
			"UUIDV5": uuid.NewV5,
		}).Parse(string(templateBytes))

	if err != nil {
		return nil, err
	}
	return outputTemplate, nil
}

func (b *bombardier) writeStatistics(
	code int, usTaken uint64,
) {
	b.latencies.Increment(usTaken)
	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)
}

func (b *bombardier) performSingleRequest() {
	code, usTaken, err := b.client.do()
	if err != nil {
		b.errors.add(err)
	}
	b.writeStatistics(code, usTaken)
}

func (b *bombardier) worker() {
	done := b.barrier.done()
	for b.barrier.tryGrabWork() {
		if b.ratelimiter.pace(done) == brk {
			break
		}
		b.performSingleRequest()
		b.barrier.jobDone()
	}
}

func (b *bombardier) barUpdater() {
	done := b.barrier.done()
	for {
		select {
		case <-done:
			b.bar.Set64(b.bar.Total)
			b.bar.Update()
			b.bar.Finish()
			if b.conf.printProgress {
				fmt.Fprintln(b.out, "Done!")
			}
			b.doneChan <- struct{}{}
			return
		default:
			current := int64(b.barrier.completed() * float64(b.bar.Total))
			b.bar.Set64(current)
			b.bar.Update()
			time.Sleep(b.bar.RefreshRate)
		}
	}
}

func (b *bombardier) rateMeter() {
	requestsInterval := 10 * time.Millisecond
	if b.conf.rate != nil {
		requestsInterval, _ = estimate(*b.conf.rate, rateLimitInterval)
	}
	requestsInterval += 10 * time.Millisecond
	ticker := time.NewTicker(requestsInterval)
	defer ticker.Stop()
	done := b.barrier.done()
	for {
		select {
		case <-ticker.C:
			b.recordRps()
			continue
		case <-done:
			b.wg.Wait()
			b.recordRps()
			b.doneChan <- struct{}{}
			return
		}
	}
}

func (b *bombardier) recordRps() {
	b.rpl.Lock()
	duration := time.Since(b.start)
	reqs := b.reqs
	b.reqs = 0
	b.start = time.Now()
	b.rpl.Unlock()

	reqsf := float64(reqs) / duration.Seconds()
	b.requests.Increment(reqsf)
}

func (b *bombardier) bombard() {
	if b.conf.printIntro {
		b.printIntro()
	}
	b.bar.Start()
	bombardmentBegin := time.Now()
	b.start = time.Now()
	for i := uint64(0); i < b.conf.numConns; i++ {
		go func() {
			defer b.wg.Done()
			b.worker()
		}()
	}
	go b.rateMeter()
	go b.barUpdater()
	b.wg.Wait()
	b.timeTaken = time.Since(bombardmentBegin)
	<-b.doneChan
	<-b.doneChan
}

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

func (b *bombardier) gatherInfo() internal.TestInfo {
	info := internal.TestInfo{
		Spec: internal.Spec{
			NumberOfConnections: b.conf.numConns,

			Method: b.conf.method,
			URL:    b.conf.url,

			Body:         b.conf.body,
			BodyFilePath: b.conf.bodyFilePath,

			CertPath: b.conf.certPath,
			KeyPath:  b.conf.keyPath,

			Stream:     b.conf.stream,
			Timeout:    b.conf.timeout,
			ClientType: internal.ClientType(b.conf.clientType),

			Rate: b.conf.rate,
		},
		Result: internal.Results{
			BytesRead:    b.bytesRead,
			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,
		},
	}

	testType := b.conf.testType()
	info.Spec.TestType = internal.TestType(testType)
	if testType == timed {
		info.Spec.TestDuration = *b.conf.duration
	} else if testType == counted {
		info.Spec.NumberOfRequests = *b.conf.numReqs
	}

	if b.conf.headers != nil {
		for _, h := range *b.conf.headers {
			info.Spec.Headers = append(info.Spec.Headers,
				internal.Header{
					Key:   h.key,
					Value: h.value,
				})
		}
	}

	for _, ewc := range b.errors.byFrequency() {
		info.Result.Errors = append(info.Result.Errors,
			internal.ErrorWithCount{
				Error: ewc.error,
				Count: ewc.count,
			})
	}

	return info
}

func (b *bombardier) printStats() {
	info := b.gatherInfo()
	err := b.template.Execute(b.out, info)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func (b *bombardier) redirectOutputTo(out io.Writer) {
	b.bar.Output = out
	b.out = out
}

func (b *bombardier) disableOutput() {
	b.redirectOutputTo(ioutil.Discard)
	b.bar.NotPrint = true
}

func main() {
	cfg, err := parser.parse(os.Args)
	if err != nil {
		fmt.Println("Error parsing the arguments:", err)
		os.Exit(exitFailure)
	}
	bombardier, err := newBombardier(cfg)
	if err != nil {
		fmt.Println("Error initializing bombardier:", err)
		os.Exit(exitFailure)
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		<-c
		bombardier.barrier.cancel()
	}()
	bombardier.bombard()
	if bombardier.conf.printResult {
		bombardier.printStats()
	}
}


================================================
FILE: bombardier_performance_test.go
================================================
package main

import (
	"flag"
	"runtime"
	"testing"
	"time"
)

var (
	serverPort = flag.String("port", "8080", "port to use for benchmarks")
	clientType = flag.String("client-type", "fasthttp",
		"client to use in benchmarks")
)

var (
	longDuration = 9001 * time.Hour
	highRate     = uint64(1000000)
)

func BenchmarkBombardierSingleReqPerf(b *testing.B) {
	addr := "localhost:" + *serverPort
	benchmarkFireRequest(config{
		numConns:       defaultNumberOfConns,
		numReqs:        nil,
		duration:       &longDuration,
		url:            ParseURLOrPanic("http://" + addr),
		headers:        new(headersList),
		timeout:        defaultTimeout,
		method:         "GET",
		body:           "",
		printLatencies: false,
		clientType:     clientTypeFromString(*clientType),
		format:         knownFormat("json"),
	}, b)
}

func BenchmarkBombardierRateLimitPerf(b *testing.B) {
	addr := "localhost:" + *serverPort
	benchmarkFireRequest(config{
		numConns:       defaultNumberOfConns,
		numReqs:        nil,
		duration:       &longDuration,
		url:            ParseURLOrPanic("http://" + addr),
		headers:        new(headersList),
		timeout:        defaultTimeout,
		method:         "GET",
		body:           "",
		printLatencies: false,
		rate:           &highRate,
		clientType:     clientTypeFromString(*clientType),
		format:         knownFormat("json"),
	}, b)
}

func benchmarkFireRequest(c config, bm *testing.B) {
	b, e := newBombardier(c)
	if e != nil {
		bm.Error(e)
	}
	b.disableOutput()
	bm.SetParallelism(int(defaultNumberOfConns) / runtime.NumCPU())
	bm.ResetTimer()
	bm.RunParallel(func(pb *testing.PB) {
		done := b.barrier.done()
		for pb.Next() {
			b.ratelimiter.pace(done)
			b.performSingleRequest()
		}
	})
}


================================================
FILE: bombardier_test.go
================================================
package main

import (
	"bytes"
	"container/ring"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"reflect"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func TestBombardierShouldFireSpecifiedNumberOfRequests(t *testing.T) {
	testAllClients(t, testBombardierShouldFireSpecifiedNumberOfRequests)
}

func testBombardierShouldFireSpecifiedNumberOfRequests(
	clientType clientTyp, t *testing.T,
) {
	reqsReceived := uint64(0)
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			atomic.AddUint64(&reqsReceived, 1)
		}),
	)
	defer s.Close()
	numReqs := uint64(100)
	noHeaders := new(headersList)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		url:        ParseURLOrPanic(s.URL),
		headers:    noHeaders,
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
	if reqsReceived != numReqs {
		t.Fail()
	}
}

func TestBombardierShouldFinish(t *testing.T) {
	testAllClients(t, testBombardierShouldFinish)
}

func testBombardierShouldFinish(clientType clientTyp, t *testing.T) {
	reqsReceived := uint64(0)
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			atomic.AddUint64(&reqsReceived, 1)
		}),
	)
	defer s.Close()
	noHeaders := new(headersList)
	desiredTestDuration := 1 * time.Second
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		duration:   &desiredTestDuration,
		url:        ParseURLOrPanic(s.URL),
		headers:    noHeaders,
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	waitCh := make(chan struct{})
	go func() {
		b.bombard()
		waitCh <- struct{}{}
	}()
	select {
	case <-waitCh:
	// Do nothing here
	case <-time.After(desiredTestDuration + 5*time.Second):
		t.Fail()
	}
	if reqsReceived == 0 {
		t.Fail()
	}
}

func TestBombardierShouldSendHeaders(t *testing.T) {
	testAllClients(t, testBombardierShouldSendHeaders)
}

func testBombardierShouldSendHeaders(clientType clientTyp, t *testing.T) {
	requestHeaders := headersList([]header{
		{"Header1", "Value1"},
		{"Header-Two", "value-two"},
	})

	// It's a bit hacky, but FastHTTP can't send Host header correctly
	// as of now
	if clientType != fhttp {
		requestHeaders = append(requestHeaders, header{"Host", "web"})
	}

	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			for _, h := range requestHeaders {
				av := r.Header.Get(h.key)
				if h.key == "Host" {
					av = r.Host
				}
				if av != h.value {
					t.Logf("%q <-> %q", av, h.value)
					t.Fail()
				}
			}
		}),
	)
	defer s.Close()
	numReqs := uint64(1)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		url:        ParseURLOrPanic(s.URL),
		headers:    &requestHeaders,
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
}

func TestBombardierHTTPCodeRecording(t *testing.T) {
	testAllClients(t, testBombardierHTTPCodeRecording)
}

func testBombardierHTTPCodeRecording(clientType clientTyp, t *testing.T) {
	cs := []int{200, 302, 404, 505, 606, 707}
	codes := ring.New(len(cs))
	for _, v := range cs {
		codes.Value = v
		codes = codes.Next()
	}
	codes = codes.Next()
	var m sync.Mutex
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			m.Lock()
			nextCode := codes.Value.(int)
			codes = codes.Next()
			m.Unlock()
			if nextCode/100 == 3 {
				rw.Header().Set("Location", "http://localhost:666")
			}
			rw.WriteHeader(nextCode)
		}),
	)
	defer s.Close()
	eachCodeCount := uint64(10)
	numReqs := uint64(len(cs)) * eachCodeCount
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
	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},
	}
	for _, e := range expectation {
		if e.reqsGot != e.expected {
			t.Error(e.name, e.reqsGot, e.expected)
		}
	}
	t.Logf("%+v", b.errors.byFrequency())
}

func TestBombardierTimeoutRecoding(t *testing.T) {
	testAllClients(t, testBombardierTimeoutRecoding)
}

func testBombardierTimeoutRecoding(clientType clientTyp, t *testing.T) {
	shortTimeout := 10 * time.Millisecond
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			time.Sleep(shortTimeout * 10)
		}),
	)
	defer s.Close()
	numReqs := uint64(10)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		duration:   nil,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    shortTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
	if b.errors.sum() != numReqs {
		t.Fail()
	}
}

func TestBombardierThroughputRecording(t *testing.T) {
	testAllClients(t, testBombardierThroughputRecording)
}

func testBombardierThroughputRecording(clientType clientTyp, t *testing.T) {
	responseSize := 1024
	response := bytes.Repeat([]byte{'a'}, responseSize)
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	numReqs := uint64(10)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
	if b.bytesRead == 0 || b.bytesWritten == 0 {
		t.Error(b.bytesRead, b.bytesWritten)
	}
}

func TestBombardierStatsPrinting(t *testing.T) {
	responseSize := 1024
	response := bytes.Repeat([]byte{'a'}, responseSize)
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	numReqs := uint64(10)
	b, e := newBombardier(config{
		numConns:       defaultNumberOfConns,
		numReqs:        &numReqs,
		url:            ParseURLOrPanic(s.URL),
		headers:        new(headersList),
		timeout:        defaultTimeout,
		method:         "GET",
		body:           "",
		printLatencies: true,
		printIntro:     true,
		printProgress:  true,
		printResult:    true,
		format:         knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	dummy := errors.New("dummy error")
	b.errors.add(dummy)

	out := new(bytes.Buffer)
	b.redirectOutputTo(out)
	b.bombard()

	b.printStats()
	l := out.Len()
	// Here we only test if anything is written
	if l == 0 {
		t.Fail()
	}
}

func TestBombardierErrorIfFailToReadClientCert(t *testing.T) {
	numReqs := uint64(10)
	_, e := newBombardier(config{
		numConns:       defaultNumberOfConns,
		numReqs:        &numReqs,
		url:            ParseURLOrPanic("http://localhost"),
		headers:        new(headersList),
		timeout:        defaultTimeout,
		method:         "GET",
		body:           "",
		printLatencies: true,
		certPath:       "certPath",
		keyPath:        "keyPath",
		format:         knownFormat("plain-text"),
	})
	if e == nil {
		t.Fail()
	}
}

func TestBombardierClientCerts(t *testing.T) {
	testAllClients(t, testBombardierClientCerts)
}

func testBombardierClientCerts(clientType clientTyp, t *testing.T) {
	clientCert, err := tls.LoadX509KeyPair("testclient.cert", "testclient.key")
	if err != nil {
		t.Error(err)
		return
	}

	clientX509Cert, err := x509.ParseCertificate(clientCert.Certificate[0])
	if err != nil {
		t.Error(err)
		return
	}

	servertCert, err := tls.LoadX509KeyPair("testserver.cert", "testserver.key")
	if err != nil {
		t.Error(err)
		return
	}

	tlsConfig := &tls.Config{
		ClientAuth:   tls.RequireAnyClientCert,
		Certificates: []tls.Certificate{servertCert},
	}

	server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		certs := r.TLS.PeerCertificates
		if numCerts := len(certs); numCerts != 1 {
			t.Errorf("expected 1 cert, but got %v", numCerts)
			rw.WriteHeader(http.StatusBadRequest)
			return
		}
		cert := certs[0]
		if !cert.Equal(clientX509Cert) {
			t.Error("certificates don't match")
			rw.WriteHeader(http.StatusBadRequest)
			return
		}
		rw.WriteHeader(http.StatusOK)
	}))

	server.TLS = tlsConfig
	server.StartTLS()

	singleRequest := uint64(1)
	b, e := newBombardier(config{
		numConns:       defaultNumberOfConns,
		numReqs:        &singleRequest,
		url:            ParseURLOrPanic(server.URL),
		headers:        new(headersList),
		timeout:        defaultTimeout,
		method:         "GET",
		body:           "",
		printLatencies: true,
		certPath:       "testclient.cert",
		keyPath:        "testclient.key",
		insecure:       true,
		clientType:     clientType,
		format:         knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	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)
	}

	server.Close()
}

func TestBombardierRateLimiting(t *testing.T) {
	testAllClients(t, testBombardierRateLimiting)
}

func testBombardierRateLimiting(clientType clientTyp, t *testing.T) {
	responseSize := 1024
	response := bytes.Repeat([]byte{'a'}, responseSize)
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			_, err := rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	rate := uint64(5000)
	testDuration := 1 * time.Second
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		duration:   &testDuration,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		rate:       &rate,
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	b.disableOutput()
	b.bombard()
	if float64(b.req2xx) < float64(rate)*0.75 ||
		float64(b.req2xx) > float64(rate)*1.25 {
		t.Error(rate, b.req2xx)
	}
}

func testAllClients(parent *testing.T, testFun func(clientTyp, *testing.T)) {
	clients := []clientTyp{fhttp, nhttp1, nhttp2}
	for _, ct := range clients {
		parent.Run(ct.String(), func(t *testing.T) {
			testFun(ct, t)
		})
	}
}

func TestBombardierSendsBody(t *testing.T) {
	testAllClients(t, testBombardierSendsBody)
}

func testBombardierSendsBody(clientType clientTyp, t *testing.T) {
	response := []byte("OK")
	requestBody := "abracadabra"
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Error(err)
				return
			}
			if string(body) != requestBody {
				t.Errorf("Expected %v, but got %v", requestBody, string(body))
			}
			_, err = rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	one := uint64(1)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &one,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    defaultTimeout,
		method:     "POST",
		body:       requestBody,
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	b.disableOutput()
	b.bombard()
}

func TestBombardierSendsBodyFromFile(t *testing.T) {
	testAllClients(t, testBombardierSendsBodyFromFile)
}

func testBombardierSendsBodyFromFile(clientType clientTyp, t *testing.T) {
	response := []byte("OK")
	bodyPath := "testbody.txt"
	requestBody, err := ioutil.ReadFile(bodyPath)
	if err != nil {
		t.Error(err)
		return
	}
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Error(err)
				return
			}
			if string(body) != string(requestBody) {
				t.Errorf("Expected %v, but got %v", string(requestBody), string(body))
			}
			_, err = rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	one := uint64(1)
	b, e := newBombardier(config{
		numConns:     defaultNumberOfConns,
		numReqs:      &one,
		url:          ParseURLOrPanic(s.URL),
		headers:      new(headersList),
		timeout:      defaultTimeout,
		method:       "POST",
		bodyFilePath: bodyPath,
		clientType:   clientType,
		format:       knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	b.disableOutput()
	b.bombard()
}

func TestBombardierFileDoesntExist(t *testing.T) {
	bodyPath := "/does/not/exist.forreal"
	_, e := newBombardier(config{
		numConns:     defaultNumberOfConns,
		url:          ParseURLOrPanic("http://example.com"),
		headers:      new(headersList),
		timeout:      defaultTimeout,
		method:       "POST",
		bodyFilePath: bodyPath,
		format:       knownFormat("plain-text"),
	})
	_, ok := e.(*os.PathError)
	if !ok {
		t.Errorf("Expected to get PathError, but got %v", e)
	}
}

func TestBombardierStreamsBody(t *testing.T) {
	testAllClients(t, testBombardierStreamsBody)
}

func testBombardierStreamsBody(clientType clientTyp, t *testing.T) {
	response := []byte("OK")
	requestBody := "abracadabra"
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			if te := r.TransferEncoding; !reflect.DeepEqual(te, []string{"chunked"}) {
				t.Errorf("Expected chunked transfer encoding, but got %v", te)
			}
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Error(err)
				return
			}
			if string(body) != requestBody {
				t.Errorf("Expected %v, but got %v", requestBody, string(body))
			}
			_, err = rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	one := uint64(1)
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &one,
		url:        ParseURLOrPanic(s.URL),
		headers:    new(headersList),
		timeout:    defaultTimeout,
		method:     "POST",
		body:       requestBody,
		stream:     true,
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	b.disableOutput()
	b.bombard()
}

func TestBombardierStreamsBodyFromFile(t *testing.T) {
	testAllClients(t, testBombardierStreamsBodyFromFile)
}

func testBombardierStreamsBodyFromFile(clientType clientTyp, t *testing.T) {
	response := []byte("OK")
	bodyPath := "testbody.txt"
	requestBody, err := ioutil.ReadFile(bodyPath)
	if err != nil {
		t.Error(err)
		return
	}
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			if te := r.TransferEncoding; !reflect.DeepEqual(te, []string{"chunked"}) {
				t.Errorf("Expected chunked transfer encoding, but got %v", te)
			}
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Error(err)
				return
			}
			if string(body) != string(requestBody) {
				t.Errorf("Expected %v, but got %v", string(requestBody), string(body))
			}
			_, err = rw.Write(response)
			if err != nil {
				t.Error(err)
			}
		}),
	)
	defer s.Close()
	one := uint64(1)
	b, e := newBombardier(config{
		numConns:     defaultNumberOfConns,
		numReqs:      &one,
		url:          ParseURLOrPanic(s.URL),
		headers:      new(headersList),
		timeout:      defaultTimeout,
		method:       "POST",
		bodyFilePath: bodyPath,
		stream:       true,
		clientType:   clientType,
		format:       knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
		return
	}
	b.disableOutput()
	b.bombard()
}

func TestBombardierShouldSendCustomHostHeader(t *testing.T) {
	testAllClients(t, testBombardierShouldSendCustomHostHeader)
}

func testBombardierShouldSendCustomHostHeader(
	clientType clientTyp, t *testing.T,
) {
	host := "custom-host"
	s := httptest.NewServer(
		http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			if r.Host != host {
				t.Errorf("Host must be %q, but it's %q", host, r.Host)
			}
		}),
	)
	defer s.Close()
	numReqs := uint64(100)
	headers := headersList([]header{
		{"Host", host},
	})
	b, e := newBombardier(config{
		numConns:   defaultNumberOfConns,
		numReqs:    &numReqs,
		url:        ParseURLOrPanic(s.URL),
		headers:    &headers,
		timeout:    defaultTimeout,
		method:     "GET",
		body:       "",
		clientType: clientType,
		format:     knownFormat("plain-text"),
	})
	if e != nil {
		t.Error(e)
	}
	b.disableOutput()
	b.bombard()
}


================================================
FILE: build.py
================================================
import argparse
import os
import subprocess

platforms = [
    ("darwin", "amd64"),
    ("darwin", "arm64"),
    ("freebsd", "386"),
    ("freebsd", "amd64"),
    ("freebsd", "arm"),
    ("linux", "386"),
    ("linux", "amd64"),
    ("linux", "arm"),
    ("linux", "arm64"),
    ("netbsd", "386"),
    ("netbsd", "amd64"),
    ("netbsd", "arm"),
    ("openbsd", "386"),
    ("openbsd", "amd64"),
    ("openbsd", "arm"),
    ("openbsd", "arm64"),
    ("windows", "386"),
    ("windows", "amd64"),
    ("windows", "arm64"),
]


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Auxilary build script.")
    parser.add_argument("-v", "--version", default="unspecified",
                        type=str, help="string used as a version when building binaries")
    args = parser.parse_args()
    version = args.version
    for (build_os, build_arch) in platforms:
        ext = ""
        if build_os == "windows":
            ext = ".exe"
        build_env = os.environ.copy()
        build_env["GOOS"] = build_os
        build_env["GOARCH"] = build_arch
        subprocess.run(["go", "build", "-ldflags", "-s -w -X main.version=%s" %
                        version, "-o", "bombardier-%s-%s%s" % (build_os, build_arch, ext)], env=build_env)


================================================
FILE: client_cert.go
================================================
package main

import (
	"crypto/tls"
)

// readClientCert - helper function to read client certificate
// from pem formatted certPath and keyPath files
func readClientCert(certPath, keyPath string) ([]tls.Certificate, error) {
	// load keypair
	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
	return []tls.Certificate{cert}, err
}

// generateTLSConfig - helper function to generate a TLS configuration based on
// config
func generateTLSConfig(c config) (*tls.Config, error) {
	var (
		certs []tls.Certificate
		err   error
	)
	// This assumes that the caller has validated that either both or none of
	// the c.certPath and c.keyPath are set.
	if c.certPath != "" && c.keyPath != "" {
		certs, err = readClientCert(c.certPath, c.keyPath)
		if err != nil {
			return nil, err
		}
	}

	// Disable gas warning, because InsecureSkipVerify may be set to true
	// for the purpose of testing
	/* #nosec */
	tlsConfig := &tls.Config{
		InsecureSkipVerify: c.insecure,
		Certificates:       certs,
	}
	return tlsConfig, nil
}


================================================
FILE: client_cert_test.go
================================================
package main

import (
	"testing"
)

func TestGenerateTLSConfig(t *testing.T) {
	expectations := []struct {
		certPath string
		keyPath  string
		errIsNil bool
	}{
		{
			certPath: "testclient.cert",
			keyPath:  "testclient.key",
			errIsNil: true,
		},
		{
			certPath: "doesnotexist.pem",
			keyPath:  "doesnotexist.pem",
			errIsNil: false,
		},
		{
			certPath: "",
			keyPath:  "",
			errIsNil: true,
		},
	}
	for _, e := range expectations {
		_, r := generateTLSConfig(
			config{
				url:      ParseURLOrPanic("https://doesnt.exist.com"),
				certPath: e.certPath,
				keyPath:  e.keyPath,
			},
		)
		if (r == nil) != e.errIsNil {
			t.Error(e.certPath, e.keyPath, r)
		}
	}
}


================================================
FILE: clients.go
================================================
package main

import (
	"crypto/tls"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/valyala/fasthttp"
)

type client interface {
	do() (code int, usTaken uint64, err error)
}

type bodyStreamProducer func() (io.ReadCloser, error)

type clientOpts struct {
	HTTP2 bool

	maxConns          uint64
	timeout           time.Duration
	tlsConfig         *tls.Config
	disableKeepAlives bool

	requestURL *url.URL
	headers    *headersList
	method     string

	body    *string
	bodProd bodyStreamProducer

	bytesRead, bytesWritten *int64
}

type fasthttpClient struct {
	client *fasthttp.Client

	headers *fasthttp.RequestHeader
	uri     *fasthttp.URI
	method  string

	body    *string
	bodProd bodyStreamProducer
}

func newFastHTTPClient(opts *clientOpts) client {
	c := new(fasthttpClient)
	uri := fasthttp.AcquireURI()
	if err := uri.Parse(
		[]byte(opts.requestURL.Host),
		[]byte(opts.requestURL.String()),
	); err != nil {
		// opts.requestURL must always be valid
		panic(err)
	}
	c.uri = uri
	c.client = &fasthttp.Client{
		MaxConnsPerHost:               int(opts.maxConns),
		ReadTimeout:                   opts.timeout,
		WriteTimeout:                  opts.timeout,
		DisableHeaderNamesNormalizing: true,
		TLSConfig:                     opts.tlsConfig,
		Dial: fasthttpDialFunc(
			opts.bytesRead, opts.bytesWritten,
			opts.timeout,
		),
	}
	c.headers = headersToFastHTTPHeaders(opts.headers)
	c.method, c.body = opts.method, opts.body
	c.bodProd = opts.bodProd
	return client(c)
}

func (c *fasthttpClient) do() (
	code int, usTaken uint64, err error,
) {
	// prepare the request
	req := fasthttp.AcquireRequest()
	resp := fasthttp.AcquireResponse()
	if c.headers != nil {
		c.headers.CopyTo(&req.Header)
	}
	req.Header.SetMethod(c.method)
	req.SetURI(c.uri)
	req.UseHostHeader = true
	if c.body != nil {
		req.SetBodyString(*c.body)
	} else {
		bs, bserr := c.bodProd()
		if bserr != nil {
			return 0, 0, bserr
		}
		req.SetBodyStream(bs, -1)
	}

	// fire the request
	start := time.Now()
	err = c.client.Do(req, resp)
	if err != nil {
		code = -1
	} else {
		code = resp.StatusCode()
	}
	usTaken = uint64(time.Since(start).Nanoseconds() / 1000)

	// release resources
	fasthttp.ReleaseRequest(req)
	fasthttp.ReleaseResponse(resp)

	return
}

type httpClient struct {
	client *http.Client

	headers http.Header
	url     *url.URL
	method  string

	body    *string
	bodProd bodyStreamProducer
}

func newHTTPClient(opts *clientOpts) client {
	c := new(httpClient)
	tr := &http.Transport{
		TLSClientConfig:     opts.tlsConfig,
		MaxIdleConnsPerHost: int(opts.maxConns),
		DisableKeepAlives:   opts.disableKeepAlives,
		ForceAttemptHTTP2:   opts.HTTP2,
		DialContext:         httpDialContextFunc(opts.bytesRead, opts.bytesWritten, opts.timeout),
	}

	cl := &http.Client{
		Transport: tr,
		Timeout:   opts.timeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	c.client = cl

	c.headers = headersToHTTPHeaders(opts.headers)
	c.method, c.body, c.bodProd = opts.method, opts.body, opts.bodProd
	c.url = opts.requestURL

	return client(c)
}

func (c *httpClient) do() (
	code int, usTaken uint64, err error,
) {
	req := &http.Request{}

	req.Header = c.headers
	req.Method = c.method
	req.URL = c.url

	if host := req.Header.Get("Host"); host != "" {
		req.Host = host
	}

	if c.body != nil {
		br := strings.NewReader(*c.body)
		req.ContentLength = int64(len(*c.body))
		req.Body = ioutil.NopCloser(br)
	} else {
		bs, bserr := c.bodProd()
		if bserr != nil {
			return 0, 0, bserr
		}
		req.Body = bs
	}

	start := time.Now()
	resp, err := c.client.Do(req)
	if err != nil {
		code = -1
	} else {
		code = resp.StatusCode

		_, berr := io.Copy(ioutil.Discard, resp.Body)
		if berr != nil {
			err = berr
		}

		if cerr := resp.Body.Close(); cerr != nil {
			err = cerr
		}
	}
	usTaken = uint64(time.Since(start).Nanoseconds() / 1000)

	return
}

func headersToFastHTTPHeaders(h *headersList) *fasthttp.RequestHeader {
	if len(*h) == 0 {
		return nil
	}
	res := new(fasthttp.RequestHeader)
	for _, header := range *h {
		res.Set(header.key, header.value)
	}
	return res
}

func headersToHTTPHeaders(h *headersList) http.Header {
	if len(*h) == 0 {
		return http.Header{}
	}
	headers := http.Header{}

	for _, header := range *h {
		headers[header.key] = []string{header.value}
	}
	return headers
}


================================================
FILE: clients_test.go
================================================
package main

import (
	"bytes"
	"crypto/tls"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"

	"github.com/goware/urlx"
)

func TestShouldReturnNilIfNoHeadersWhereSet(t *testing.T) {
	h := new(headersList)
	if headersToFastHTTPHeaders(h) != nil {
		t.Fail()
	}
}

func TestShouldReturnEmptyHeadersIfNoHeaadersWhereSet(t *testing.T) {
	h := new(headersList)
	if len(headersToHTTPHeaders(h)) != 0 {
		t.Fail()
	}
}

func TestShouldProperlyConvertToHttpHeaders(t *testing.T) {
	h := new(headersList)
	for _, hs := range []string{
		"Content-Type: application/json", "Custom-Header: xxx42xxx",
	} {
		if err := h.Set(hs); err != nil {
			t.Error(err)
		}
	}
	fh := headersToFastHTTPHeaders(h)
	{
		e, a := []byte("application/json"), fh.Peek("Content-Type")
		if !bytes.Equal(e, a) {
			t.Errorf("Expected %v, but got %v", e, a)
		}
	}
	if e, a := []byte("xxx42xxx"), fh.Peek("Custom-Header"); !bytes.Equal(e, a) {
		t.Errorf("Expected %v, but got %v", e, a)
	}

	nh := headersToHTTPHeaders(h)
	{
		e, a := "application/json", nh.Get("Content-Type")
		if e != a {
			t.Errorf("Expected %v, but got %v", e, a)
		}
	}
	if e, a := "xxx42xxx", nh.Get("Custom-Header"); e != a {
		t.Errorf("Expected %v, but got %v", e, a)
	}
}

func TestHTTP2Client(t *testing.T) {
	responseSize := 1024
	response := bytes.Repeat([]byte{'a'}, responseSize)
	s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !r.ProtoAtLeast(2, 0) {
			t.Errorf("invalid HTTP proto version: %v", r.Proto)
		}

		w.WriteHeader(http.StatusOK)
		_, err := w.Write(response)
		if err != nil {
			t.Error(err)
		}
	}))
	s.EnableHTTP2 = true
	s.TLS = &tls.Config{
		InsecureSkipVerify: true,
	}
	s.StartTLS()
	defer s.Close()

	bytesRead, bytesWritten := int64(0), int64(0)
	requestURL, err := urlx.Parse(s.URL)
	if err != nil {
		t.Fatal(err)
	}
	c := newHTTPClient(&clientOpts{
		HTTP2: true,

		headers:    new(headersList),
		requestURL: requestURL,
		method:     "GET",
		tlsConfig: &tls.Config{
			InsecureSkipVerify: true,
		},

		body: new(string),

		bytesRead:    &bytesRead,
		bytesWritten: &bytesWritten,
	})
	code, _, err := c.do()
	if err != nil {
		t.Error(err)
		return
	}
	if code != http.StatusOK {
		t.Errorf("invalid response code: %v", code)
	}
	if atomic.LoadInt64(&bytesRead) == 0 {
		t.Errorf("invalid response size: %v", bytesRead)
	}
	if atomic.LoadInt64(&bytesWritten) == 0 {
		t.Errorf("empty request of size: %v", bytesWritten)
	}
}

func TestHTTP1Clients(t *testing.T) {
	responseSize := 1024
	response := bytes.Repeat([]byte{'a'}, responseSize)
	s := httptest.NewServer(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			if r.ProtoMajor != 1 {
				t.Errorf("invalid HTTP proto version: %v", r.Proto)
			}

			w.WriteHeader(http.StatusOK)
			_, err := w.Write(response)
			if err != nil {
				t.Error(err)
			}
		},
	))
	defer s.Close()

	bytesRead, bytesWritten := int64(0), int64(0)
	requestURL, err := urlx.Parse(s.URL)
	if err != nil {
		t.Fatal(err)
	}
	cc := &clientOpts{
		HTTP2: false,

		headers:    new(headersList),
		requestURL: requestURL,
		method:     "GET",

		body: new(string),

		bytesRead:    &bytesRead,
		bytesWritten: &bytesWritten,
	}
	clients := []client{
		newHTTPClient(cc),
		newFastHTTPClient(cc),
	}
	for _, c := range clients {
		bytesRead, bytesWritten = 0, 0
		code, _, err := c.do()
		if err != nil {
			t.Error(err)
			return
		}
		if code != http.StatusOK {
			t.Errorf("invalid response code: %v", code)
		}
		if bytesRead == 0 {
			t.Errorf("invalid response size: %v", bytesRead)
		}
		if bytesWritten == 0 {
			t.Errorf("empty request of size: %v", bytesWritten)
		}
	}
}


================================================
FILE: cmd/utils/simplebenchserver/doc.go
================================================
/*
Simple HTTP server used for benchmarking.

Following options are available:

	    --help         Show context-sensitive help (also try --help-long and
	                   --help-man).
	-p, --port="8080"  port to use for benchmarks
	-s, --size=1024    size of response in bytes
*/
package main


================================================
FILE: cmd/utils/simplebenchserver/main.go
================================================
package main

import (
	"bytes"
	"log"
	"net/http"

	"github.com/alecthomas/kingpin"
	"github.com/valyala/fasthttp"
)

var serverPort = kingpin.Flag("port", "port to use for benchmarks").
	Default("8080").
	Short('p').
	String()
var responseSize = kingpin.Flag("size", "size of response in bytes").
	Default("1024").
	Short('s').
	Uint()
var stdHTTP = kingpin.Flag("std-http", "use standard http library").
	Default("false").
	Bool()

func main() {
	kingpin.Parse()
	response := bytes.Repeat([]byte("a"), int(*responseSize))
	addr := "localhost:" + *serverPort
	log.Println("Starting HTTP server on:", addr)
	var lserr error
	if *stdHTTP {
		lserr = http.ListenAndServe(addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			_, werr := w.Write(response)
			if werr != nil {
				log.Println(werr)
			}
		}))
	} else {
		lserr = fasthttp.ListenAndServe(addr, func(c *fasthttp.RequestCtx) {
			_, werr := c.Write(response)
			if werr != nil {
				log.Println(werr)
			}
		})
	}
	if lserr != nil {
		log.Println(lserr)
	}
}


================================================
FILE: common.go
================================================
package main

import (
	"errors"
	"net/url"
	"sort"
	"time"

	"github.com/goware/urlx"
)

const (
	decBase = 10

	rateLimitInterval = 10 * time.Millisecond
	oneSecond         = 1 * time.Second

	exitFailure = 1
)

var (
	version = "unspecified"

	emptyConf = config{}
	parser    = newKingpinParser()

	defaultTestDuration  = 10 * time.Second
	defaultNumberOfConns = uint64(125)
	defaultTimeout       = 2 * time.Second

	httpMethods = []string{
		"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
		"PATCH",
	}
	cantHaveBody = []string{"HEAD"}

	errUnsupportedScheme    = errors.New("unsupported scheme")
	errInvalidNumberOfConns = errors.New(
		"invalid number of connections(must be > 0)")
	errInvalidNumberOfRequests = errors.New(
		"invalid number of requests(must be > 0)")
	errInvalidTestDuration = errors.New(
		"invalid test duration(must be >= 1s)")
	errNegativeTimeout = errors.New(
		"timeout can't be negative")
	errBodyNotAllowed = errors.New(
		"HEAD requests cannot have body")
	errNoPathToCert = errors.New(
		"no Path to TLS Client Certificate")
	errNoPathToKey = errors.New(
		"no Path to TLS Client Certificate Private Key")
	errZeroRate = errors.New(
		"rate can't be less than 1")
	errBodyProvidedTwice = errors.New("use either --body or --body-file")

	errInvalidHeaderFormat = errors.New("invalid header format")
	errEmptyPrintSpec      = errors.New(
		"empty print spec is not a valid print spec")
)

func ParseURLOrPanic(s string) *url.URL {
	u, err := urlx.Parse(s)
	if err != nil {
		panic(err)
	}
	return u
}

func init() {
	sort.Strings(httpMethods)
	sort.Strings(cantHaveBody)
}


================================================
FILE: completion_barriers.go
================================================
package main

import (
	"sync"
	"sync/atomic"
	"time"
)

type completionBarrier interface {
	completed() float64
	tryGrabWork() bool
	jobDone()
	done() <-chan struct{}
	cancel()
}

type countingCompletionBarrier struct {
	numReqs, reqsGrabbed, reqsDone uint64
	doneChan                       chan struct{}
	closeOnce                      sync.Once
}

func newCountingCompletionBarrier(numReqs uint64) completionBarrier {
	c := new(countingCompletionBarrier)
	c.reqsDone, c.reqsGrabbed, c.numReqs = 0, 0, numReqs
	c.doneChan = make(chan struct{})
	return completionBarrier(c)
}

func (c *countingCompletionBarrier) tryGrabWork() bool {
	select {
	case <-c.doneChan:
		return false
	default:
		reqsDone := atomic.AddUint64(&c.reqsGrabbed, 1)
		return reqsDone <= c.numReqs
	}
}

func (c *countingCompletionBarrier) jobDone() {
	reqsDone := atomic.AddUint64(&c.reqsDone, 1)
	if reqsDone == c.numReqs {
		c.closeOnce.Do(func() {
			close(c.doneChan)
		})
	}
}

func (c *countingCompletionBarrier) done() <-chan struct{} {
	return c.doneChan
}

func (c *countingCompletionBarrier) cancel() {
	c.closeOnce.Do(func() {
		close(c.doneChan)
	})
}

func (c *countingCompletionBarrier) completed() float64 {
	select {
	case <-c.doneChan:
		return 1.0
	default:
		reqsDone := atomic.LoadUint64(&c.reqsDone)
		return float64(reqsDone) / float64(c.numReqs)
	}
}

type timedCompletionBarrier struct {
	doneChan  chan struct{}
	closeOnce sync.Once
	start     time.Time
	duration  time.Duration
}

func newTimedCompletionBarrier(duration time.Duration) completionBarrier {
	if duration < 0 {
		panic("timedCompletionBarrier: negative duration")
	}
	c := new(timedCompletionBarrier)
	c.doneChan = make(chan struct{})
	c.start = time.Now()
	c.duration = duration
	go func() {
		time.AfterFunc(duration, func() {
			c.closeOnce.Do(func() {
				close(c.doneChan)
			})
		})
	}()
	return completionBarrier(c)
}

func (c *timedCompletionBarrier) tryGrabWork() bool {
	select {
	case <-c.doneChan:
		return false
	default:
		return true
	}
}

func (c *timedCompletionBarrier) jobDone() {
}

func (c *timedCompletionBarrier) done() <-chan struct{} {
	return c.doneChan
}

func (c *timedCompletionBarrier) cancel() {
	c.closeOnce.Do(func() {
		close(c.doneChan)
	})
}

func (c *timedCompletionBarrier) completed() float64 {
	select {
	case <-c.doneChan:
		return 1.0
	default:
		return float64(time.Since(c.start).Nanoseconds()) /
			float64(c.duration.Nanoseconds())
	}
}


================================================
FILE: completion_barriers_test.go
================================================
package main

import (
	"math"
	"testing"
	"time"
)

func TestCouintingCompletionBarrierWait(t *testing.T) {
	parties := uint64(10)
	b := newCountingCompletionBarrier(1000)
	for i := uint64(0); i < parties; i++ {
		go func() {
			for b.tryGrabWork() {
				b.jobDone()
			}
		}()
	}
	wc := make(chan struct{})
	go func() {
		<-b.done()
		wc <- struct{}{}
	}()
	select {
	case <-wc:
		return
	case <-time.After(100 * time.Millisecond):
		t.Fail()
	}
}

func TestTimedCompletionBarrierWait(t *testing.T) {
	parties := uint64(10)
	duration := 100 * time.Millisecond
	timeout := duration * 2
	err := 15 * time.Millisecond
	sleepDuration := 2 * time.Millisecond
	b := newTimedCompletionBarrier(duration)
	for i := uint64(0); i < parties; i++ {
		go func() {
			for b.tryGrabWork() {
				time.Sleep(sleepDuration)
				b.jobDone()
			}
		}()
	}
	wc := make(chan time.Duration)
	go func() {
		start := time.Now()
		<-b.done()
		wc <- time.Since(start)
	}()
	select {
	case actual := <-wc:
		if !approximatelyEqual(duration, actual, sleepDuration+err) {
			t.Errorf("Expected to run %v, but ran %v instead", duration, actual)
		}
	case <-time.After(timeout):
		t.Error("Barrier hanged")
	}
}

func TestTimeBarrierCancel(t *testing.T) {
	b := newTimedCompletionBarrier(9000 * time.Second)
	sleepTime := 100 * time.Millisecond
	go func() {
		time.Sleep(sleepTime)
		b.cancel()
	}()
	select {
	case <-b.done():
		if c := b.completed(); c != 1.0 {
			t.Error(c)
		}
	case <-time.After(sleepTime * 2):
		t.Fail()
	}
}

func TestCountedBarrierCancel(t *testing.T) {
	parties := uint64(10)
	b := newCountingCompletionBarrier(math.MaxUint64)
	sleepTime := 100 * time.Millisecond
	for i := uint64(0); i < parties; i++ {
		go func() {
			for b.tryGrabWork() {
				b.jobDone()
			}
		}()
	}
	go func() {
		time.Sleep(sleepTime)
		b.cancel()
	}()
	select {
	case <-b.done():
		if c := b.completed(); c != 1.0 {
			t.Error(c)
		}
	case <-time.After(5 * time.Second):
		t.Fail()
	}
}

func TestTimeBarrierPanicOnBadDuration(t *testing.T) {
	defer func() {
		r := recover()
		if r == nil {
			t.Error("shouldn't be empty")
			t.Fail()
		}
	}()
	newTimedCompletionBarrier(-1 * time.Second)
	t.Error("unreachable")
	t.Fail()
}

func approximatelyEqual(expected, actual, err time.Duration) bool {
	return expected-err < actual && actual < expected+err
}


================================================
FILE: config.go
================================================
package main

import (
	"fmt"
	"net/url"
	"sort"
	"time"
)

type config struct {
	numConns                  uint64
	numReqs                   *uint64
	disableKeepAlives         bool
	duration                  *time.Duration
	url                       *url.URL
	method, certPath, keyPath string
	body, bodyFilePath        string
	stream                    bool
	headers                   *headersList
	timeout                   time.Duration
	// TODO(codesenberg): printLatencies should probably be
	// re(named&maked) into printPercentiles or even let
	// users provide their own percentiles and not just
	// calculate for [0.5, 0.75, 0.9, 0.99]
	printLatencies, insecure bool
	rate                     *uint64
	clientType               clientTyp

	printIntro, printProgress, printResult bool

	format format
}

type testTyp int

const (
	none testTyp = iota
	timed
	counted
)

type invalidHTTPMethodError struct {
	method string
}

func (i *invalidHTTPMethodError) Error() string {
	return fmt.Sprintf("Unknown HTTP method: %v", i.method)
}

func (c *config) checkArgs() error {
	c.checkOrSetDefaultTestType()

	checks := []func() error{
		c.checkURL,
		c.checkRate,
		c.checkRunParameters,
		c.checkTimeoutDuration,
		c.checkHTTPParameters,
		c.checkCertPaths,
	}

	for _, check := range checks {
		if err := check(); err != nil {
			return err
		}
	}

	return nil
}

func (c *config) checkOrSetDefaultTestType() {
	if c.testType() == none {
		c.duration = &defaultTestDuration
	}
}

func (c *config) testType() testTyp {
	typ := none
	if c.numReqs != nil {
		typ = counted
	} else if c.duration != nil {
		typ = timed
	}
	return typ
}

func (c *config) checkURL() error {
	if c.url.Scheme != "http" && c.url.Scheme != "https" {
		return errUnsupportedScheme
	}
	return nil
}

func (c *config) checkRate() error {
	if c.rate != nil && *c.rate < 1 {
		return errZeroRate
	}
	return nil
}

func (c *config) checkRunParameters() error {
	if c.numConns < uint64(1) {
		return errInvalidNumberOfConns
	}
	if c.testType() == counted && *c.numReqs < uint64(1) {
		return errInvalidNumberOfRequests
	}
	if c.testType() == timed && *c.duration < time.Second {
		return errInvalidTestDuration
	}
	return nil
}

func (c *config) checkTimeoutDuration() error {
	if c.timeout < 0 {
		return errNegativeTimeout
	}
	return nil
}

func (c *config) checkHTTPParameters() error {
	if !allowedHTTPMethod(c.method) {
		return &invalidHTTPMethodError{method: c.method}
	}
	if !canHaveBody(c.method) && (c.body != "" || c.bodyFilePath != "") {
		return errBodyNotAllowed
	}
	if c.body != "" && c.bodyFilePath != "" {
		return errBodyProvidedTwice
	}
	return nil
}

func (c *config) checkCertPaths() error {
	if c.certPath != "" && c.keyPath == "" {
		return errNoPathToKey
	} else if c.certPath == "" && c.keyPath != "" {
		return errNoPathToCert
	}
	return nil
}

func (c *config) timeoutMillis() uint64 {
	return uint64(c.timeout.Nanoseconds() / 1000)
}

func allowedHTTPMethod(method string) bool {
	i := sort.SearchStrings(httpMethods, method)
	return i < len(httpMethods) && httpMethods[i] == method
}

func canHaveBody(method string) bool {
	i := sort.SearchStrings(cantHaveBody, method)
	return !(i < len(cantHaveBody) && cantHaveBody[i] == method)
}

type clientTyp int

const (
	fhttp clientTyp = iota
	nhttp1
	nhttp2
)

func (ct clientTyp) String() string {
	switch ct {
	case fhttp:
		return "FastHTTP"
	case nhttp1:
		return "net/http v1.x"
	case nhttp2:
		return "net/http v2.0"
	}
	return "unknown client"
}


================================================
FILE: config_test.go
================================================
package main

import (
	"testing"
	"time"
)

var (
	defaultNumberOfReqs = uint64(10000)
)

func TestCanHaveBody(t *testing.T) {
	expectations := []struct {
		in  string
		out bool
	}{
		{"HEAD", false},
		{"GET", true},
		{"POST", true},
		{"PUT", true},
		{"DELETE", true},
		{"OPTIONS", true},
	}
	for _, e := range expectations {
		if r := canHaveBody(e.in); r != e.out {
			t.Error(e.in, e.out, r)
		}
	}
}

func TestAllowedHttpMethod(t *testing.T) {
	expectations := []struct {
		in  string
		out bool
	}{
		{"GET", true},
		{"POST", true},
		{"PUT", true},
		{"DELETE", true},
		{"HEAD", true},
		{"OPTIONS", true},
		{"TRUNCATE", false},
	}
	for _, e := range expectations {
		if r := allowedHTTPMethod(e.in); r != e.out {
			t.Logf("Expected f(%v) = %v, but got %v", e.in, e.out, r)
			t.Fail()
		}
	}
}

func TestCheckArgs(t *testing.T) {
	invalidNumberOfReqs := uint64(0)
	smallTestDuration := 99 * time.Millisecond
	negativeTimeoutDuration := -1 * time.Second
	noHeaders := new(headersList)
	zeroRate := uint64(0)
	expectations := []struct {
		in  config
		out error
	}{
		{
			config{
				numConns: 0,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				format:   knownFormat("plain-text"),
			},
			errInvalidNumberOfConns,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &invalidNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				format:   knownFormat("plain-text"),
			},
			errInvalidNumberOfRequests,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  nil,
				duration: &smallTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				format:   knownFormat("plain-text"),
			},
			errInvalidTestDuration,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  negativeTimeoutDuration,
				method:   "GET",
				body:     "",
				format:   knownFormat("plain-text"),
			},
			errNegativeTimeout,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "HEAD",
				body:     "BODY",
				format:   knownFormat("plain-text"),
			},
			errBodyNotAllowed,
		},
		{
			config{
				numConns:     defaultNumberOfConns,
				numReqs:      &defaultNumberOfReqs,
				duration:     &defaultTestDuration,
				url:          ParseURLOrPanic("http://localhost:8080"),
				headers:      noHeaders,
				timeout:      defaultTimeout,
				method:       "HEAD",
				bodyFilePath: "testbody.txt",
				format:       knownFormat("plain-text"),
			},
			errBodyNotAllowed,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "BODY",
				format:   knownFormat("plain-text"),
			},
			nil,
		},
		{
			config{
				numConns:     defaultNumberOfConns,
				numReqs:      &defaultNumberOfReqs,
				duration:     &defaultTestDuration,
				url:          ParseURLOrPanic("http://localhost:8080"),
				headers:      noHeaders,
				timeout:      defaultTimeout,
				method:       "GET",
				bodyFilePath: "testbody.txt",
				format:       knownFormat("plain-text"),
			},
			nil,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				format:   knownFormat("plain-text"),
			},
			nil,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				certPath: "test_cert.pem",
				keyPath:  "",
				format:   knownFormat("plain-text"),
			},
			errNoPathToKey,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				body:     "",
				certPath: "",
				keyPath:  "test_key.pem",
				format:   knownFormat("plain-text"),
			},
			errNoPathToCert,
		},
		{
			config{
				numConns: defaultNumberOfConns,
				numReqs:  &defaultNumberOfReqs,
				duration: &defaultTestDuration,
				url:      ParseURLOrPanic("http://localhost:8080"),
				headers:  noHeaders,
				timeout:  defaultTimeout,
				method:   "GET",
				rate:     &zeroRate,
				format:   knownFormat("plain-text"),
			},
			errZeroRate,
		},
		{
			config{
				numConns:     defaultNumberOfConns,
				numReqs:      &defaultNumberOfReqs,
				duration:     &defaultTestDuration,
				url:          ParseURLOrPanic("http://localhost:8080"),
				headers:      noHeaders,
				timeout:      defaultTimeout,
				method:       "POST",
				body:         "abracadabra",
				bodyFilePath: "testbody.txt",
				format:       knownFormat("plain-text"),
			},
			errBodyProvidedTwice,
		},
	}
	for _, e := range expectations {
		if r := e.in.checkArgs(); r != e.out {
			t.Logf("Expected (%v).checkArgs to return %v, but got %v", e.in, e.out, r)
			t.Fail()
		}
		if _, r := newBombardier(e.in); r != e.out {
			t.Logf("Expected newBombardier(%v) to return %v, but got %v", e.in, e.out, r)
			t.Fail()
		}
	}
}

func TestCheckArgsUnsupportedURLScheme(t *testing.T) {
	c := config{
		numConns: defaultNumberOfConns,
		numReqs:  &defaultNumberOfReqs,
		duration: &defaultTestDuration,
		url:      ParseURLOrPanic("ftp://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "GET",
		body:     "",
	}
	if c.checkArgs() != errUnsupportedScheme {
		t.Fail()
	}
}

func TestCheckArgsInvalidRequestMethod(t *testing.T) {
	c := config{
		numConns: defaultNumberOfConns,
		numReqs:  &defaultNumberOfReqs,
		duration: &defaultTestDuration,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "ABRACADABRA",
		body:     "",
	}
	e := c.checkArgs()
	if e == nil {
		t.Fail()
	}
	if _, ok := e.(*invalidHTTPMethodError); !ok {
		t.Fail()
	}
}

func TestCheckArgsTestType(t *testing.T) {
	countedConfig := config{
		numConns: defaultNumberOfConns,
		numReqs:  &defaultNumberOfReqs,
		duration: nil,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "GET",
		body:     "",
	}
	timedConfig := config{
		numConns: defaultNumberOfConns,
		numReqs:  nil,
		duration: &defaultTestDuration,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "GET",
		body:     "",
	}
	both := config{
		numConns: defaultNumberOfConns,
		numReqs:  &defaultNumberOfReqs,
		duration: &defaultTestDuration,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "GET",
		body:     "",
	}
	defaultConfig := config{
		numConns: defaultNumberOfConns,
		numReqs:  nil,
		duration: nil,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  defaultTimeout,
		method:   "GET",
		body:     "",
	}
	if err := countedConfig.checkArgs(); err != nil ||
		countedConfig.testType() != counted {
		t.Fail()
	}
	if err := timedConfig.checkArgs(); err != nil ||
		timedConfig.testType() != timed {
		t.Fail()
	}
	if err := both.checkArgs(); err != nil ||
		both.testType() != counted {
		t.Fail()
	}
	if err := defaultConfig.checkArgs(); err != nil ||
		defaultConfig.testType() != timed ||
		defaultConfig.duration != &defaultTestDuration {
		t.Fail()
	}
}

func TestTimeoutMillis(t *testing.T) {
	defaultConfig := config{
		numConns: defaultNumberOfConns,
		numReqs:  nil,
		duration: nil,
		url:      ParseURLOrPanic("http://localhost:8080"),
		headers:  nil,
		timeout:  2 * time.Second,
		method:   "GET",
		body:     "",
	}
	if defaultConfig.timeoutMillis() != 2000000 {
		t.Fail()
	}
}

func TestInvalidHTTPMethodError(t *testing.T) {
	invalidMethod := "NOSUCHMETHOD"
	want := "Unknown HTTP method: " + invalidMethod
	err := &invalidHTTPMethodError{invalidMethod}
	if got := err.Error(); got != want {
		t.Error(got, want)
	}
}

func TestClientTypToStringConversion(t *testing.T) {
	expectations := []struct {
		in  clientTyp
		out string
	}{
		{fhttp, "FastHTTP"},
		{nhttp1, "net/http v1.x"},
		{nhttp2, "net/http v2.0"},
		{42, "unknown client"},
	}
	for _, exp := range expectations {
		act := exp.in.String()
		if act != exp.out {
			t.Errorf("Expected %v, but got %v", exp.out, act)
		}
	}
}

func clientTypeFromString(s string) clientTyp {
	switch s {
	case "fasthttp":
		return fhttp
	case "http1":
		return nhttp1
	case "http2":
		return nhttp2
	default:
		return fhttp
	}
}


================================================
FILE: dialer.go
================================================
package main

import (
	"context"
	"net"
	"sync/atomic"
	"time"
)

type countingConn struct {
	net.Conn
	bytesRead, bytesWritten *int64
}

func (cc *countingConn) Read(b []byte) (n int, err error) {
	n, err = cc.Conn.Read(b)

	if err == nil {
		atomic.AddInt64(cc.bytesRead, int64(n))
	}

	return
}

func (cc *countingConn) Write(b []byte) (n int, err error) {
	n, err = cc.Conn.Write(b)

	if err == nil {
		atomic.AddInt64(cc.bytesWritten, int64(n))
	}

	return
}

var fasthttpDialFunc = func(
	bytesRead, bytesWritten *int64,
	dialTimeout time.Duration,
) func(string) (net.Conn, error) {
	return func(address string) (net.Conn, error) {
		conn, err := net.DialTimeout("tcp", address, dialTimeout)
		if err != nil {
			return nil, err
		}

		wrappedConn := &countingConn{
			Conn:         conn,
			bytesRead:    bytesRead,
			bytesWritten: bytesWritten,
		}

		return wrappedConn, nil
	}
}

var httpDialContextFunc = func(
	bytesRead, bytesWritten *int64,
	dialTimeout time.Duration,
) func(context.Context, string, string) (net.Conn, error) {
	dialer := &net.Dialer{Timeout: dialTimeout}
	return func(ctx context.Context, network, address string) (net.Conn, error) {
		conn, err := dialer.DialContext(ctx, network, address)
		if err != nil {
			return nil, err
		}

		wrappedConn := &countingConn{
			Conn:         conn,
			bytesRead:    bytesRead,
			bytesWritten: bytesWritten,
		}

		return wrappedConn, nil
	}
}


================================================
FILE: doc.go
================================================
/*
Command line utility bombardier is a fast cross-platform HTTP
benchmarking tool written in Go.

Installation with Go 1.17+:

	go install github.com/codesenberg/bombardier@latest

Installation with older versions of Go:

	go get -u github.com/codesenberg/bombardier

Usage:

	bombardier [<flags>] <url>

Flags:

	    --help                  Show context-sensitive help (also try --help-long
	                            and --help-man).
	    --version               Show application version.
	-c, --connections=125       Maximum number of concurrent connections
	-t, --timeout=2s            Socket/request timeout
	-l, --latencies             Print latency statistics
	-m, --method=GET            Request method
	-b, --body=""               Request body
	-f, --body-file=""          File to use as request body
	-s, --stream                Specify whether to stream body using chunked
	                            transfer encoding or to serve it from memory
	    --cert=""               Path to the client's TLS Certificate
	    --key=""                Path to the client's TLS Certificate Private Key
	-k, --insecure              Controls whether a client verifies the server's
	                            certificate chain and host name
	-H, --header="K: V" ...     HTTP headers to use(can be repeated)
	-n, --requests=[pos. int.]  Number of requests
	-d, --duration=10s          Duration of test
	-r, --rate=[pos. int.]      Rate limit in requests per second
	    --fasthttp              Use fasthttp client
	    --http1                 Use net/http client with forced HTTP/1.x
	    --http2                 Use net/http client with enabled HTTP/2.0
	-p, --print=<spec>          Specifies what to output. Comma-separated list of
	                            values 'intro' (short: 'i'), 'progress' (short:
	                            'p'), 'result' (short: 'r'). Examples:

	                              * i,p,r (prints everything)
	                              * intro,result (intro & result)
	                              * r (result only)
	                              * result (same as above)
	-q, --no-print              Don't output anything
	-o, --format=<spec>         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, which uses Go's
	                            text/template syntax, prefixed with 'path:' string
	                            (without single quotes), i.e.
	                            "path:/some/path/to/your.template" or
	                            "path:C:\some\path\to\your.template" in case of
	                            Windows. Formats understood by bombardier are:

	                              * plain-text (short: pt)
	                              * json (short: j)

Args:

	<url>  Target's URL

For detailed documentation on user-defined templates see
documentation for package github.com/codesenberg/bombardier/template.
Link (GoDoc):
https://godoc.org/github.com/codesenberg/bombardier/template
*/
package main


================================================
FILE: docs/CONTRIBUTING.md
================================================
### Contribution Guidelines
For relevant info on how to format commit messages and check the code before submitting pull requests see [PULL_REQUEST_TEMPLATE](https://github.com/codesenberg/bombardier/blob/master/.github/PULL_REQUEST_TEMPLATE.md).

### Reporting issues
Please open an issue if you would like to discuss anything that could be improved, have a suggestion or want to report a bug.
In latter case refer to [ISSUE_TEMPLATE](https://github.com/codesenberg/bombardier/blob/master/.github/ISSUE_TEMPLATE.md).


================================================
FILE: error_map.go
================================================
package main

import (
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
)

type errorMap struct {
	mu sync.RWMutex
	m  map[string]*uint64
}

func newErrorMap() *errorMap {
	em := new(errorMap)
	em.m = make(map[string]*uint64)
	return em
}

func (e *errorMap) add(err error) {
	s := err.Error()
	e.mu.RLock()
	c, ok := e.m[s]
	e.mu.RUnlock()
	if !ok {
		e.mu.Lock()
		c, ok = e.m[s]
		if !ok {
			c = new(uint64)
			e.m[s] = c
		}
		e.mu.Unlock()
	}
	atomic.AddUint64(c, 1)
}

func (e *errorMap) get(err error) uint64 {
	s := err.Error()
	e.mu.RLock()
	defer e.mu.RUnlock()
	c := e.m[s]
	if c == nil {
		return uint64(0)
	}
	return *c
}

func (e *errorMap) sum() uint64 {
	e.mu.RLock()
	defer e.mu.RUnlock()
	sum := uint64(0)
	for _, v := range e.m {
		sum += *v
	}
	return sum
}

type errorWithCount struct {
	error string
	count uint64
}

func (ewc *errorWithCount) String() string {
	return "<" + ewc.error + ":" +
		strconv.FormatUint(ewc.count, decBase) + ">"
}

type errorsByFrequency []*errorWithCount

func (ebf errorsByFrequency) Len() int {
	return len(ebf)
}

func (ebf errorsByFrequency) Less(i, j int) bool {
	return ebf[i].count > ebf[j].count
}

func (ebf errorsByFrequency) Swap(i, j int) {
	ebf[i], ebf[j] = ebf[j], ebf[i]
}

func (e *errorMap) byFrequency() errorsByFrequency {
	e.mu.RLock()
	byFreq := make(errorsByFrequency, 0, len(e.m))
	for err, count := range e.m {
		byFreq = append(byFreq, &errorWithCount{err, *count})
	}
	e.mu.RUnlock()
	sort.Sort(byFreq)
	return byFreq
}


================================================
FILE: error_map_test.go
================================================
package main

import (
	"errors"
	"reflect"
	"testing"
)

func TestErrorMapAdd(t *testing.T) {
	m := newErrorMap()
	err := errors.New("add")
	m.add(err)
	if c := m.get(err); c != 1 {
		t.Error(c)
	}
}

func TestErrorMapGet(t *testing.T) {
	m := newErrorMap()
	err := errors.New("get")
	if c := m.get(err); c != 0 {
		t.Error(c)
	}
}

func TestByFrequency(t *testing.T) {
	m := newErrorMap()
	a := errors.New("A")
	b := errors.New("B")
	c := errors.New("C")
	m.add(a)
	m.add(a)
	m.add(b)
	m.add(b)
	m.add(b)
	m.add(c)
	e := errorsByFrequency{
		{"B", 3},
		{"A", 2},
		{"C", 1},
	}
	if a := m.byFrequency(); !reflect.DeepEqual(a, e) {
		t.Logf("Expected: %+v", e)
		t.Logf("Got: %+v", a)
		t.Fail()
	}
}

func TestErrorWithCountToStringConversion(t *testing.T) {
	ewc := errorWithCount{"A", 1}
	exp := "<A:1>"
	if act := ewc.String(); act != exp {
		t.Logf("Expected: %+v", exp)
		t.Logf("Got: %+v", act)
		t.Fail()
	}
}

func BenchmarkErrorMapAdd(b *testing.B) {
	m := newErrorMap()
	err := errors.New("benchmark")
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			m.add(err)
		}
	})
}

func BenchmarkErrorMapGet(b *testing.B) {
	m := newErrorMap()
	err := errors.New("benchmark")
	m.add(err)
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			m.get(err)
		}
	})
}


================================================
FILE: flags.go
================================================
package main

import (
	"strconv"
	"time"
)

const (
	nilStr = "nil"
)

type nullableUint64 struct {
	val *uint64
}

func (n *nullableUint64) String() string {
	if n.val == nil {
		return nilStr
	}
	return strconv.FormatUint(*n.val, 10)
}

func (n *nullableUint64) Set(value string) error {
	res, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return err
	}
	n.val = new(uint64)
	*n.val = res
	return nil
}

type nullableDuration struct {
	val *time.Duration
}

func (n *nullableDuration) String() string {
	if n.val == nil {
		return nilStr
	}
	return n.val.String()
}

func (n *nullableDuration) Set(value string) error {
	res, err := time.ParseDuration(value)
	if err != nil {
		return err
	}
	n.val = &res
	return nil
}

type nullableString struct {
	val *string
}

func (n *nullableString) String() string {
	if n.val == nil {
		return nilStr
	}
	return *n.val
}

func (n *nullableString) Set(value string) error {
	n.val = new(string)
	*n.val = value
	return nil
}


================================================
FILE: flags_test.go
================================================
package main

import (
	"math"
	"math/big"
	"strconv"
	"testing"
	"time"
)

func TestNullableUint64ConversionToString(t *testing.T) {
	nilint := &nullableUint64{val: nil}
	if s := nilint.String(); s != "nil" {
		t.Errorf("Expected \"nil\", but got %v", s)
	}
	v := uint64(42)
	nonnilint := &nullableUint64{val: &v}
	if s, e := nonnilint.String(), strconv.FormatUint(v, 10); s != e {
		t.Errorf("Expected %v, but got %v", e, s)
	}
}

func TestNullableUint64Parsing(t *testing.T) {
	n := &nullableUint64{}
	if err := n.Set("-1"); err == nil {
		t.Error("Should fail on negative values")
	}
	if err := n.Set(""); err == nil {
		t.Error("Should fail on empty string")
	}
	b := big.NewInt(0)
	b.SetUint64(math.MaxUint64)
	b.Add(b, big.NewInt(1))
	if err := n.Set(b.String()); err == nil {
		t.Error("Should fail on large values")
	}
	max := strconv.FormatUint(math.MaxUint64, 10)
	if err := n.Set(max); err != nil || *n.val != uint64(18446744073709551615) {
		t.Error("Shouldn't fail on max value")
	}
}

func TestNullableDurationConversionToString(t *testing.T) {
	nildur := &nullableDuration{val: nil}
	if s := nildur.String(); s != "nil" {
		t.Errorf("Expected \"nil\", but got %v", s)
	}
	d := time.Second
	nonnildir := &nullableDuration{val: &d}
	if s := nonnildir.String(); s != "1s" {
		t.Errorf("Expected 1s, but got %v", s)
	}
}

func TestNullableDurationParsing(t *testing.T) {
	d := &nullableDuration{}
	if err := d.Set(""); err == nil {
		t.Error("Should fail on empty string")
	}
	if err := d.Set("Wubba lubba dub dub!"); err == nil {
		t.Error("Should fail on incorrect values")
	}
	if err := d.Set("1s"); err != nil || *d.val != time.Second {
		t.Error("Shouldn't fail on correct values")
	}
}

func TestNullableStringConversionToString(t *testing.T) {
	ns := new(nullableString)
	if act := ns.String(); act != nilStr {
		t.Error("Unset nullableString should convert to \"nil\"")
	}
	someVal := "someval"
	if err := ns.Set(someVal); err != nil {
		t.Errorf("Couldn't set nullableString to %q", someVal)
	}
	if act := ns.String(); act != someVal {
		t.Errorf("Expected %q, but got %q", someVal, act)
	}
}


================================================
FILE: format.go
================================================
package main

import (
	"fmt"
)

type units struct {
	scale uint64
	base  string
	units []string
}

var (
	binaryUnits = &units{
		scale: 1024,
		base:  "",
		units: []string{"KB", "MB", "GB", "TB", "PB"},
	}
	timeUnitsUs = &units{
		scale: 1000,
		base:  "us",
		units: []string{"ms", "s"},
	}
	timeUnitsS = &units{
		scale: 60,
		base:  "s",
		units: []string{"m", "h"},
	}
)

func formatUnits(n float64, m *units, prec int) string {
	amt := n
	unit := m.base

	scale := float64(m.scale) * 0.85

	for i := 0; i < len(m.units) && amt >= scale; i++ {
		amt /= float64(m.scale)
		unit = m.units[i]
	}
	return fmt.Sprintf("%.*f%s", prec, amt, unit)
}

func formatBinary(n float64) string {
	return formatUnits(n, binaryUnits, 2)
}

func formatTimeUs(n float64) string {
	units := timeUnitsUs
	if n >= 1000000.0 {
		n /= 1000000.0
		units = timeUnitsS
	}
	return formatUnits(n, units, 2)
}


================================================
FILE: format_test.go
================================================
package main

import (
	"testing"
)

const (
	KB = 1024
	MB = KB * 1024
	GB = MB * 1024

	K = 1000
	M = K * 1000
)

func TestShouldFormatBinary(t *testing.T) {
	expectations := []struct {
		in  float64
		out string
	}{
		{10.0, "10.00"},
		{10.001, "10.00"},
		{1.0 * KB, "1.00KB"},
		{1.2 * KB, "1.20KB"},
		{1.202 * KB, "1.20KB"},
		{5 * KB, "5.00KB"},
		{1.0 * MB, "1.00MB"},
		{1.3 * MB, "1.30MB"},
		{1.302 * MB, "1.30MB"},
		{6 * MB, "6.00MB"},
		{1.0 * GB, "1.00GB"},
		{1.4 * GB, "1.40GB"},
		{1.402 * GB, "1.40GB"},
		{7 * GB, "7.00GB"},
	}
	for _, e := range expectations {
		actual := formatBinary(e.in)
		expected := e.out
		if expected != actual {
			t.Errorf("Expected \"%v\", but got \"%v\"", expected, actual)
		}
	}
}

func TestShouldFormatUs(t *testing.T) {
	expectations := []struct {
		in  float64
		out string
	}{
		{20, "20.00us"},
		{22.222, "22.22us"},
		{20 * K, "20.00ms"},
		{20 * M, "20.00s"},
		{60 * M, "1.00m"},
		{10 * 60 * M, "10.00m"},
		{90 * 60 * M, "1.50h"},
	}
	for _, e := range expectations {
		actual := formatTimeUs(e.in)
		expected := e.out
		if expected != actual {
			t.Errorf("Expected \"%v\", but got \"%v\"", expected, actual)
		}
	}
}


================================================
FILE: go.mod
================================================
module github.com/codesenberg/bombardier

go 1.22

toolchain go1.24.0

require (
	github.com/alecthomas/kingpin v2.2.6+incompatible
	github.com/cheggaaa/pb v1.0.29
	github.com/codesenberg/concurrent v0.0.0-20180531114123-64560cfcf964
	github.com/goware/urlx v0.3.2
	github.com/juju/ratelimit v1.0.2
	github.com/satori/go.uuid v1.2.0
	github.com/valyala/fasthttp v1.59.0
)

require (
	github.com/PuerkitoBio/purell v1.2.1 // indirect
	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
	github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
	github.com/andybalholm/brotli v1.1.1 // indirect
	github.com/klauspost/compress v1.18.0 // indirect
	github.com/mattn/go-runewidth v0.0.16 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	golang.org/x/net v0.35.0 // indirect
	golang.org/x/sys v0.30.0 // indirect
	golang.org/x/text v0.22.0 // indirect
)


================================================
FILE: go.sum
================================================
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28=
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
github.com/codesenberg/concurrent v0.0.0-20180531114123-64560cfcf964 h1:9MVnbW3h0Dl4E2oADqwyvODphl9jY1r5HMtcB8U5mGs=
github.com/codesenberg/concurrent v0.0.0-20180531114123-64560cfcf964/go.mod h1:82C6OyVM6eVk7qpBAZXE9uszHUuXWJMHHOeY+b/CSIA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/goware/urlx v0.3.2 h1:gdoo4kBHlkqZNaf6XlQ12LGtQOmpKJrR04Rc3RnpJEo=
github.com/goware/urlx v0.3.2/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: headers.go
================================================
package main

import (
	"fmt"
	"strings"
)

type header struct {
	key, value string
}

type headersList []header

func (h *headersList) String() string {
	return fmt.Sprint(*h)
}

func (h *headersList) IsCumulative() bool {
	return true
}

func (h *headersList) Set(value string) error {
	res := strings.SplitN(value, ":", 2)
	if len(res) != 2 {
		return errInvalidHeaderFormat
	}
	*h = append(*h, header{
		res[0], strings.Trim(res[1], " "),
	})
	return nil
}


================================================
FILE: headers_test.go
================================================
package main

import (
	"testing"
)

func TestHeadersToStringConversion(t *testing.T) {
	expectations := []struct {
		in  headersList
		out string
	}{
		{
			[]header{},
			"[]",
		},
		{
			[]header{
				{"Key1", "Value1"},
				{"Key2", "Value2"}},
			"[{Key1 Value1} {Key2 Value2}]",
		},
	}
	for _, e := range expectations {
		actual := e.in.String()
		expected := e.out
		if expected != actual {
			t.Errorf("Expected \"%v\", but got \"%v\"", expected, actual)
		}
	}
}

func TestShouldErrorOnInvalidFormat(t *testing.T) {
	h := new(headersList)
	if err := h.Set("Yaba daba do"); err == nil {
		t.Error("Should fail on strings without colon")
	}
}

func TestShouldProperlyAddValidHeaders(t *testing.T) {
	h := new(headersList)
	for _, hs := range []string{"Key1: Value1", "Key2: Value2"} {
		if err := h.Set(hs); err != nil {
			t.Error(err)
		}
	}
	e := []header{{"Key1", "Value1"}, {"Key2", "Value2"}}
	for i, v := range *h {
		if e[i] != v {
			t.Fail()
		}
	}
}

func TestShouldTrimHeaderValues(t *testing.T) {
	h := new(headersList)
	if err := h.Set("Key:   Value   "); err != nil {
		t.Error(err)
	}
	if (*h)[0].key != "Key" || (*h)[0].value != "Value" {
		t.Fail()
	}
}


================================================
FILE: internal/test_info.go
================================================
package internal

import (
	"math"
	"net/url"
	"sort"
	"time"
)

// TestInfo holds information about what specification was used
// to perform the test and results of the test.
type TestInfo struct {
	Spec   Spec
	Result Results
}

// Header represents HTTP header.
type Header struct {
	Key, Value string
}

// Spec contains information about test performed.
type Spec struct {
	NumberOfConnections uint64

	TestType         TestType
	NumberOfRequests uint64
	TestDuration     time.Duration

	Method string
	URL    *url.URL

	Headers []Header

	Body         string
	BodyFilePath string

	CertPath string
	KeyPath  string

	Stream     bool
	Timeout    time.Duration
	ClientType ClientType

	Rate *uint64
}

// RequestURL returns URL as string.
func (s Spec) RequestURL() string {
	return s.URL.String()
}

// IsTimedTest tells if the test was limited by time.
func (s Spec) IsTimedTest() bool {
	return s.TestType == ByTime
}

// IsTestWithNumberOfReqs tells if the test was limited by the number
// of requests.
func (s Spec) IsTestWithNumberOfReqs() bool {
	return s.TestType == ByNumberOfReqs
}

// IsFastHTTP tells whether fasthttp were used as HTTP client to
// perform the test.
func (s Spec) IsFastHTTP() bool {
	return s.ClientType == FastHTTP
}

// IsNetHTTPV1 tells whether Go's default net/http library and
// HTTP/1.x were used to perform the test.
func (s Spec) IsNetHTTPV1() bool {
	return s.ClientType == NetHTTP1
}

// IsNetHTTPV2 tells whether Go's default net/http library and
// HTTP/1.x (or HTTP/2.0, if possible) were used to perform the test.
func (s Spec) IsNetHTTPV2() bool {
	return s.ClientType == NetHTTP2
}

// Results holds results of the test.
type Results struct {
	BytesRead, BytesWritten int64
	TimeTaken               time.Duration

	Req1XX, Req2XX, Req3XX, Req4XX, Req5XX uint64
	Others                                 uint64

	Errors []ErrorWithCount

	Latencies ReadonlyUint64Histogram
	Requests  ReadonlyFloat64Histogram
}

// ReadonlyUint64Histogram is a readonly histogram with uint64 keys
type ReadonlyUint64Histogram interface {
	Get(uint64) uint64
	VisitAll(func(uint64, uint64) bool)
	Count() uint64
}

// ReadonlyFloat64Histogram is a readonly histogram with float64 keys
type ReadonlyFloat64Histogram interface {
	Get(float64) uint64
	VisitAll(func(float64, uint64) bool)
	Count() uint64
}

// Throughput returns total throughput (read + write) in bytes per
// second
func (r Results) Throughput() float64 {
	return float64(r.BytesRead+r.BytesWritten) / r.TimeTaken.Seconds()
}

// LatenciesStats contains statistical information about latencies.
type LatenciesStats struct {
	// These are in microseconds
	Mean   float64
	Stddev float64
	Max    float64

	// This is  map[0.0 <= p <= 1.0 (percentile)]microseconds
	Percentiles map[float64]uint64
}

// LatenciesStats performs various statistical calculations on
// latencies.
func (r Results) LatenciesStats(percentiles []float64) *LatenciesStats {
	h := r.Latencies
	sum := uint64(0)
	count := uint64(0)
	max := uint64(0)
	pairs := make([]struct{ k, v uint64 }, 0, h.Count())

	// Gather all the data
	h.VisitAll(func(f uint64, c uint64) bool {
		if f > max {
			max = f
		}
		sum += f * c
		count += c
		pairs = append(pairs, struct{ k, v uint64 }{f, c})
		return true
	})
	if count < 1 {
		return nil
	}

	// Calculate percentiles
	sort.Slice(pairs, func(i, j int) bool {
		return pairs[i].k < pairs[j].k
	})
	percentilesMap := map[float64]uint64{}
	for _, pc := range percentiles {
		if _, calculated := percentilesMap[pc]; calculated {
			continue
		}
		if pc < 0 || pc > 1 {
			// Drop percentiles outside of [0, 1] range
			continue
		}
		rank := uint64(pc*float64(count) + 0.5)
		total := uint64(0)
		for _, p := range pairs {
			total += p.v
			if total >= rank {
				percentilesMap[pc] = p.k
				break
			}
		}
	}

	// Calculate mean and standard deviation
	mean := float64(sum) / float64(count)
	sumOfSquares := float64(0)
	h.VisitAll(func(f uint64, c uint64) bool {
		sumOfSquares += math.Pow(float64(f)-mean, 2)
		return true
	})
	stddev := 0.0
	if count > 2 {
		stddev = math.Sqrt(sumOfSquares / float64(count))
	}
	return &LatenciesStats{
		Mean:   mean,
		Stddev: stddev,
		Max:    float64(max),

		Percentiles: percentilesMap,
	}
}

// RequestsStats contains statistical information about requests.
type RequestsStats struct {
	// These are in requests per second.
	Mean   float64
	Stddev float64
	Max    float64

	// This is  map[0.0 <= p <= 1.0 (percentile)](req-s per second)
	Percentiles map[float64]float64
}

// RequestsStats performs various statistical calculations on
// latencies.
func (r Results) RequestsStats(percentiles []float64) *RequestsStats {
	h := r.Requests
	sum := float64(0)
	count := uint64(0)
	max := float64(0)
	pairs := make([]struct {
		k float64
		v uint64
	}, 0, h.Count())

	// Gather all the data
	h.VisitAll(func(f float64, c uint64) bool {
		if math.IsInf(f, 0) || math.IsNaN(f) {
			return true
		}
		if f > max {
			max = f
		}
		sum += f * float64(c)
		count += c
		pairs = append(pairs, struct {
			k float64
			v uint64
		}{f, c})
		return true
	})
	if count < 1 {
		return nil
	}

	// Calculate percentiles
	sort.Slice(pairs, func(i, j int) bool {
		return pairs[i].k < pairs[j].k
	})
	percentilesMap := map[float64]float64{}
	for _, pc := range percentiles {
		if _, calculated := percentilesMap[pc]; calculated {
			continue
		}
		if pc < 0 || pc > 1 {
			// Drop percentiles outside of [0, 1] range
			continue
		}
		rank := uint64(pc*float64(count) + 0.5)
		total := uint64(0)
		for _, p := range pairs {
			total += p.v
			if total >= rank {
				percentilesMap[pc] = p.k
				break
			}
		}
	}

	// Calculate mean and standard deviation
	mean := sum / float64(count)
	sumOfSquares := float64(0)
	h.VisitAll(func(f float64, c uint64) bool {
		if math.IsInf(f, 0) || math.IsNaN(f) {
			return true
		}
		sumOfSquares += math.Pow(f-mean, 2)
		return true
	})
	stddev := 0.0
	if count > 2 {
		const besselCorrection = 1.0
		stddev = math.Sqrt(sumOfSquares / (float64(count) - besselCorrection))
	}
	return &RequestsStats{
		Mean:   mean,
		Stddev: stddev,
		Max:    max,

		Percentiles: percentilesMap,
	}
}

// ErrorWithCount contains error description alongside with number of
// times this error occurred.
type ErrorWithCount struct {
	Error string
	Count uint64
}

// TestType represents the type of test that were performed.
type TestType int

const (
	_ TestType = iota
	// ByTime is a test limited by durations.
	ByTime
	// ByNumberOfReqs is a test limited by number of requests
	// performed.
	ByNumberOfReqs
)

// ClientType is the type of HTTP client used in test
type ClientType int

const (
	// FastHTTP is fasthttp's HTTP client
	FastHTTP ClientType = iota
	// NetHTTP1 is Go's default HTTP client with forced HTTP/1.x
	NetHTTP1
	// NetHTTP2 is Go's default HTTP client with HTTP/2.0 permitted.
	NetHTTP2
)


================================================
FILE: limiter.go
================================================
package main

import (
	"math"
	"sync"
	"time"

	"github.com/juju/ratelimit"
)

type token uint64

const (
	brk token = iota
	cont
)

type limiter interface {
	pace(<-chan struct{}) token
}

type nooplimiter struct{}

func (n *nooplimiter) pace(<-chan struct{}) token {
	return cont
}

type bucketlimiter struct {
	limiter   *ratelimit.Bucket
	timerPool *sync.Pool
}

func newBucketLimiter(rate uint64) limiter {
	fillInterval, quantum := estimate(rate, rateLimitInterval)
	return &bucketlimiter{
		ratelimit.NewBucketWithQuantum(
			fillInterval, int64(quantum), int64(quantum),
		),
		&sync.Pool{
			New: func() interface{} {
				return time.NewTimer(math.MaxInt64)
			},
		},
	}
}

func (b *bucketlimiter) pace(done <-chan struct{}) (res token) {
	wd := b.limiter.Take(1)
	if wd <= 0 {
		return cont
	}

	timer := b.timerPool.Get().(*time.Timer)
	timer.Reset(wd)
	select {
	case <-timer.C:
		res = cont
	case <-done:
		res = brk
	}
	b.timerPool.Put(timer)
	return
}


================================================
FILE: limiter_barrier_test.go
================================================
package main

import (
	"sync"
	"sync/atomic"
	"testing"
)

func TestNoopLimiterCounterBarrierCombination(t *testing.T) {
	expectations := []uint64{
		1, 15, 50, 100, 150, 500, 1000, 1500, 5000,
	}
	done := make(chan struct{})
	for _, count := range expectations {
		b := newCountingCompletionBarrier(count)
		var lim limiter = &nooplimiter{}
		counter := uint64(0)
		numParties := 10
		var wg sync.WaitGroup
		wg.Add(numParties)
		for i := 0; i < numParties; i++ {
			go func() {
				defer wg.Done()
				for b.tryGrabWork() {
					lim.pace(done)
					atomic.AddUint64(&counter, 1)
					b.jobDone()
				}
			}()
		}
		wg.Wait()
		if counter != count {
			t.Error(count, counter)
		}
	}
}

func TestBucketLimiterCounterBarrierCombination(t *testing.T) {
	expectations := []struct {
		count, rate uint64
	}{
		{10, 100},
		{10, 1000},
		{100, 1000},
		{100, 10000},
		{1000, 10000},
		{1000, 100000},
	}
	done := make(chan struct{})
	var expWg sync.WaitGroup
	expWg.Add(len(expectations))
	for i := range expectations {
		exp := expectations[i]
		go func() {
			defer expWg.Done()
			b := newCountingCompletionBarrier(exp.count)
			lim := newBucketLimiter(exp.rate)
			counter := uint64(0)
			numParties := 10
			var wg sync.WaitGroup
			wg.Add(numParties)
			for i := 0; i < numParties; i++ {
				go func() {
					defer wg.Done()
					for b.tryGrabWork() {
						lim.pace(done)
						atomic.AddUint64(&counter, 1)
						b.jobDone()
					}
				}()
			}
			wg.Wait()
			if counter != exp.count {
				t.Error(exp.count, counter)
			}
		}()
	}
	expWg.Wait()
}


================================================
FILE: limiter_test.go
================================================
package main

import (
	"runtime"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

const maxRps = 10000000

func TestNoopLimiter(t *testing.T) {
	var lim limiter = &nooplimiter{}
	done := make(chan struct{})
	counter := uint64(0)
	var wg sync.WaitGroup
	wg.Add(int(defaultNumberOfConns))
	for i := uint64(0); i < defaultNumberOfConns; i++ {
		go func() {
			defer wg.Done()
			for {
				res := lim.pace(done)
				if res != cont {
					t.Error("nooplimiter should always return cont")
				}
				atomic.AddUint64(&counter, 1)
				select {
				case <-done:
					return
				default:
				}
			}
		}()
	}
	time.Sleep(100 * time.Millisecond)
	close(done)
	wg.Wait()
	if counter == 0 {
		t.Error("no events happened")
	}
}

func BenchmarkNoopLimiter(bm *testing.B) {
	var lim limiter = &nooplimiter{}
	done := make(chan struct{})
	bm.SetParallelism(int(defaultNumberOfConns) / runtime.NumCPU())
	bm.ResetTimer()
	bm.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			lim.pace(done)
		}
	})
}

func TestBucketLimiterLowRates(t *testing.T) {
	expectations := []struct {
		rate     uint64
		duration time.Duration
	}{
		{1, 1 * time.Second},
		{10, 1 * time.Second},
		{15, 1 * time.Second},
		{50, 1 * time.Second},
		{100, 1 * time.Second},
		{150, 1 * time.Second},
		{500, 1 * time.Second},
		{1000, 1 * time.Second},
		{1500, 1 * time.Second},
		{5000, 1 * time.Second},
	}
	for i := range expectations {
		exp := expectations[i]
		lim := newBucketLimiter(exp.rate)
		done := make(chan struct{})
		counter := uint64(0)
		waitChan := make(chan struct{})
		go func() {
			defer func() {
				waitChan <- struct{}{}
			}()
			for lim.pace(done) == cont {
				counter++
			}
		}()
		time.Sleep(exp.duration)
		close(done)
		select {
		case <-waitChan:
		case <-time.After(exp.duration + 100*time.Millisecond):
			t.Error("failed to complete: ", exp)
			return
		}
		expcounter := float64(exp.rate) * exp.duration.Seconds()
		var (
			lowerBound = 0.5 * expcounter
			upperBound = 1.2*expcounter + 5
		)
		if float64(counter) < lowerBound ||
			float64(counter) > upperBound {
			t.Errorf("(lower bound, actual, upper bound): (%11.2f, %11d, %11.2f)", lowerBound, counter, upperBound)
		}
	}
}

func TestBucketLimiterHighRates(t *testing.T) {
	expectations := []struct {
		rate     uint64
		duration time.Duration
	}{
		{100000, 100 * time.Millisecond},
		{150000, 100 * time.Millisecond},
		{200000, 100 * time.Millisecond},
		{500000, 100 * time.Millisecond},
		{1000000, 100 * time.Millisecond},
	}
	for i := range expectations {
		exp := expectations[i]
		lim := newBucketLimiter(exp.rate)
		counter := uint64(0)
		done := make(chan struct{})
		waitChan := make(chan struct{})
		go func() {
			defer func() {
				waitChan <- struct{}{}
			}()
			for lim.pace(done) == cont {
				counter++
			}
		}()
		time.Sleep(exp.duration)
		close(done)
		select {
		case <-waitChan:
		case <-time.After(exp.duration + 50*time.Millisecond):
			t.Error("failed to complete: ", exp)
			return
		}
		expcounter := float64(exp.rate) * exp.duration.Seconds()
		var (
			lowerBound = 0.5 * expcounter
			upperBound = 1.2*expcounter + 5
		)
		if float64(counter) < lowerBound ||
			float64(counter) > upperBound {
			t.Errorf("(lower bound, actual, upper bound): (%11.2f, %11d, %11.2f)", lowerBound, counter, upperBound)
		}
	}
}

func BenchmarkBucketLimiter(bm *testing.B) {
	lim := newBucketLimiter(maxRps)
	done := make(chan struct{})
	bm.SetParallelism(int(defaultNumberOfConns) / runtime.NumCPU())
	bm.ResetTimer()
	bm.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			lim.pace(done)
		}
	})
}


================================================
FILE: proxy_reader.go
================================================
package main

import "io"

type proxyReader struct {
	io.Reader
}


================================================
FILE: rateestimator.go
================================================
package main

import (
	"math/big"
	"time"
)

const (
	panicZeroRate         = "rate can't be zero"
	panicNegativeAdjustTo = "adjustTo can't be negative or zero"
)

func estimate(rate uint64, adjustTo time.Duration) (time.Duration, uint64) {
	if rate == 0 {
		panic(panicZeroRate)
	}
	if adjustTo <= 0 {
		panic(panicNegativeAdjustTo)
	}
	br := new(big.Int).SetUint64(rate)
	bd := new(big.Int).SetInt64(oneSecond.Nanoseconds())
	gcd := new(big.Int).GCD(nil, nil, br, bd).Uint64()
	nr, nd := rate/gcd, uint64(oneSecond.Nanoseconds())/gcd
	adjustInt := uint64(adjustTo.Nanoseconds())
	if nd >= adjustInt {
		return time.Duration(nd), nr
	}
	coef := adjustInt / nd
	return time.Duration(coef * nd), coef * nr
}


================================================
FILE: rateestimator_test.go
================================================
package main

import (
	"testing"
	"time"
)

func TestRateEstimatorPanicWithZeroRate(t *testing.T) {
	defer func() {
		pv, ok := recover().(string)
		if !ok {
			t.Error("expected string value")
			return
		}
		if pv != panicZeroRate {
			t.Error(panicZeroRate, pv)
		}
	}()
	_, _ = estimate(0, 10*time.Second)
	t.Error("should fail with rate == 0")
}

func TestRateEstimatorPanicWithNegativeAdjustTo(t *testing.T) {
	defer func() {
		pv, ok := recover().(string)
		if !ok {
			t.Error("expected string value")
			return
		}
		if pv != panicNegativeAdjustTo {
			t.Error(panicNegativeAdjustTo, pv)
		}
	}()
	_, _ = estimate(10, -10*time.Second)
	t.Error("should fail with adjustTo <= 0")
}

func TestRateEstimatorAccuracy(t *testing.T) {
	defer func() {
		rv := recover()
		if rv != nil {
			t.Error(rv)
		}
	}()
	expectations := []struct {
		rate                 uint64
		adjustTo             time.Duration
		expectedQuantum      uint64
		expectedFillInterval time.Duration
	}{
		{1, 100 * time.Millisecond, 1, 1 * time.Second},
		{1, 1000 * time.Millisecond, 1, 1 * time.Second},
		{1, 2000 * time.Millisecond, 2, 2 * time.Second},
		{1, 3000 * time.Millisecond, 3, 3 * time.Second},
		{4, 3000 * time.Millisecond, 12, 3 * time.Second},
		{10000, 100 * time.Millisecond, 1000, 100 * time.Millisecond},
		{100000, 100 * time.Millisecond, 10000, 100 * time.Millisecond},
		{1000000, 100 * time.Millisecond, 100000, 100 * time.Millisecond},
	}
	for _, exp := range expectations {
		actualFillInterval, actualQuantum := estimate(exp.rate, exp.adjustTo)
		if actualFillInterval != exp.expectedFillInterval ||
			actualQuantum != exp.expectedQuantum {
			t.Log("Expected: ", exp.expectedQuantum, exp.expectedFillInterval)
			t.Log("Actual: ", actualQuantum, actualFillInterval)
			t.Fail()
		}
	}
}


================================================
FILE: template/doc.go
================================================
/*
Package template documents the way user-defined output templates are
ment to be used.

User-defined templates use Go's text/template package, so you might
want to check its documentation first.
There are a bunch of helper methods available inside a template
besides those described in aforementioned documentation, namely:
  - URLString()
    Returns the URL string used for the load test.
  - WithLatencies()
    Tells whether --latencies flag were activated.
  - FormatBinary(numberOfBytes float64) string
    Converts bytes to kilo-, mega-, giga-, etc.- bytes, and
    appends appropriate suffix "KB", "MB", "GB", etc.
  - FormatTimeUs(us float64) string
    Converts microseconds to milliseconds, seconds, minutes or
    hours and appends appropriate suffix.
  - FormatTimeUsUint64(us uint64) string
    Same as above, but for uint64, since type conversions are
    not available in templates.
  - FloatsToArray(ps ...float64) []float64
    Converts a bunch of floats into array, since, again,
    type conversions are not available in templates.
  - Multiply(num, coeff float64) float64
    Arithmetics are not available inside of templates either.
  - StringToBytes(s string) []byte
    Convenience function to convert string to []byte.
  - UUIDV1() (UUID, error)
    Generates UUID Version 1, based on timestamp and
    MAC address (RFC 4122)
  - UUIDV2(domain byte) (UUID, error)
    Generates UUID Version 2, based on timestamp, MAC address
    and POSIX UID/GID (DCE 1.1)
  - UUIDV3(ns UUID, name string) UUID
    Generates UUID Version 3, based on MD5 hashing (RFC 4122)
  - UUIDV4() (UUID, error)
    Generates UUID Version 4, based on random numbers (RFC 4122)
  - UUIDV5(ns UUID, name string) UUID
    Generates UUID Version 5, based on SHA-1 hashing (RFC 4122)

The structure that gets passed to the template is documented in
the package github.com/codesenberg/bombardier/internal. The structure
of interest is TestInfo. It basically consists of Spec and Result
fields, the former contains various information about the test
(number of connections, URL, HTTP method, headers, body, rate, etc.)
performed, while the latter contains results obtained during the
execution of this test (bytes read/written, time taken, RPS, etc.).

Link to GoDoc for the structure used in template:
https://godoc.org/github.com/codesenberg/bombardier/internal#TestInfo

Examples of templates can be found in:
https://github.com/codesenberg/bombardier/blob/master/templates.go
*/
package template


================================================
FILE: templates.go
================================================
package main

import "strings"

var (
	templates = map[string][]byte{
		"plain-text": []byte(plainTextTemplate),
		"json":       []byte(jsonTemplate),
	}
)

type format interface{}
type knownFormat string

func (kf knownFormat) template() []byte {
	return templates[string(kf)]
}

type filePath string
type userDefinedTemplate filePath

func formatFromString(formatSpec string) format {
	const prefix = "path:"
	if strings.HasPrefix(formatSpec, prefix) {
		return userDefinedTemplate(formatSpec[len(prefix):])
	}
	switch formatSpec {
	case "pt", "plain-text":
		return knownFormat("plain-text")
	case "j", "json":
		return knownFormat("json")
	}
	// nil represents unknown format
	return nil
}

const (
	plainTextTemplate = `
{{- printf "%10v %10v %10v %10v" "Statistics" "Avg" "Stdev" "Max" }}
{{ with .Result.RequestsStats (FloatsToArray 0.5 0.75 0.9 0.95 0.99) }}
	{{- printf "  %-10v %10.2f %10.2f %10.2f" "Reqs/sec" .Mean .Stddev .Max -}}
{{ else }}
	{{- print "  There wasn't enough data to compute statistics for requests." }}
{{ end }}
{{ with .Result.LatenciesStats (FloatsToArray 0.5 0.75 0.9 0.95 0.99) }}
	{{- printf "  %-10v %10v %10v %10v" "Latency" (FormatTimeUs .Mean) (FormatTimeUs .Stddev) (FormatTimeUs .Max) }}
	{{- if WithLatencies }}
  		{{- "\n  Latency Distribution" }}
		{{- range $pc, $lat := .Percentiles }}
			{{- printf "\n     %2.0f%% %10s" (Multiply $pc 100) (FormatTimeUsUint64 $lat) -}}
		{{ end -}}
	{{ end }}
{{ else }}
	{{- print "  There wasn't enough data to compute statistics for latencies." }}
{{ end -}}
{{ with .Result -}}
{{ "  HTTP codes:" }}
{{ printf "    1xx - %v, 2xx - %v, 3xx - %v, 4xx - %v, 5xx - %v" .Req1XX .Req2XX .Req3XX .Req4XX .Req5XX }}
	{{- printf "\n    others - %v" .Others }}
	{{- with .Errors }}
		{{- "\n  Errors:"}}
		{{- range . }}
			{{- printf "\n    %10v - %v" .Error .Count }}
		{{- end -}}
	{{ end -}}
{{ end }}
{{ printf "  %-10v %10v/s\n" "Throughput:" (FormatBinary .Result.Throughput)}}`
	jsonTemplate = `{"spec":{
{{- with .Spec -}}
"numberOfConnections":{{ .NumberOfConnections }}

{{- if .IsTimedTest -}}
,"testType":"timed","testDurationSeconds":{{ .TestDuration.Seconds }}
{{- else -}}
,"testType":"number-of-requests","numberOfRequests":{{ .NumberOfRequests }}
{{- end -}}

,"method":"{{ .Method }}","url":{{ .RequestURL | printf "%q" }}

{{- with .Headers -}}
,"headers":[
{{- range $index, $header :=  . -}}
{{- if ne $index 0 -}},{{- end -}}
{"key":{{ .Key | printf "%q" }},"value":{{ .Value | printf "%q" }}}
{{- end -}}
]
{{- end -}}

{{- if .BodyFilePath -}}
,"bodyFilePath":{{ .BodyFilePath | printf "%q" }}
{{- else -}}
,"body":{{ .Body | printf "%q" }}
{{- end -}}

{{- if .CertPath -}}
,"certPath":{{ .CertPath | printf "%q" }}
{{- end -}}
{{- if .KeyPath -}}
,"keyPath":{{ .KeyPath | printf "%q" }}
{{- end -}}

,"stream":{{ .Stream }},"timeoutSeconds":{{ .Timeout.Seconds }}

{{- if .IsFastHTTP -}}
,"client":"fasthttp"
{{- end -}}
{{- if .IsNetHTTPV1 -}}
,"client":"net/http.v1"
{{- end -}}
{{- if .IsNetHTTPV2 -}}
,"client":"net/http.v2"
{{- end -}}

{{- with .Rate -}}
,"rate":{{ . }}
{{- end -}}
{{- end -}}
},

{{- with .Result -}}
"result":{"bytesRead":{{ .BytesRead -}}
,"bytesWritten":{{ .BytesWritten -}}
,"timeTakenSeconds":{{ .TimeTaken.Seconds -}}

,"req1xx":{{ .Req1XX -}}
,"req2xx":{{ .Req2XX -}}
,"req3xx":{{ .Req3XX -}}
,"req4xx":{{ .Req4XX -}}
,"req5xx":{{ .Req5XX -}}
,"others":{{ .Others -}}

{{- with .Errors -}}
,"errors":[
{{- range $index, $error :=  . -}}
{{- if ne $index 0 -}},{{- end -}}
{"description":{{ .Error | printf "%q" }},"count":{{ .Count }}}
{{- end -}}
]
{{- end -}}

{{- with .LatenciesStats (FloatsToArray 0.5 0.75 0.9 0.95 0.99) -}}
,"latency":{"mean":{{ .Mean -}}
,"stddev":{{ .Stddev -}}
,"max":{{ .Max -}}

{{- if WithLatencies -}}
,"percentiles":{
{{- range $pc, $lat := .Percentiles }}
{{- if ne $pc 0.5 -}},{{- end -}}
{{- printf "\"%2.0f\":%d" (Multiply $pc 100) $lat -}}
{{- end -}}
}
{{- end -}}

}
{{- end -}}

{{- with .RequestsStats (FloatsToArray 0.5 0.75 0.9 0.95 0.99) -}}
,"rps":{"mean":{{ .Mean -}}
,"stddev":{{ .Stddev -}}
,"max":{{ .Max -}}
,"percentiles":{
{{- range $pc, $rps := .Percentiles }}
{{- if ne $pc 0.5 -}},{{- end -}}
{{- printf "\"%2.0f\":%f" (Multiply $pc 100) $rps -}}
{{- end -}}
}}
{{- end -}}
}}
{{- end -}}`
)


================================================
FILE: testbody.txt
================================================
abracadabra

================================================
FILE: testclient.cert
================================================
-----BEGIN CERTIFICATE-----
MIIFITCCAwmgAwIBAgIJAMx2fpQ+fhOZMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV
BAMMG0JvbWJhcmRpZXIgQ2xpZW50IFRlc3QgQ2VydDAgFw0xNzAxMjYwNjM2MDda
GA8zMDE2MDUyOTA2MzYwN1owJjEkMCIGA1UEAwwbQm9tYmFyZGllciBDbGllbnQg
VGVzdCBDZXJ0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1TE9F36z
fsSmXbxkfNonYggVA7skrb+10iuLyey5snkOHLszEjmL3Gtux02GaqZ8u3GdfeZ/
Am2KqFnLq/YiZwYJpwpB54PAtKQqCiIcAPCENZdPzuya4bWTP+/bLiLdDY0kJhxM
rReufBL+xBOFrRhLcsW+tECGsu+d39o7JY7oWnm7IQYX7cxK1JgaFL5kmUuaoJN0
iJhB7V3JIQJMC68Yr5dzrYdzwzc1uxm43Y696HYAPkygf41ZoHo5UKWCI9V9M0iz
8oUoWrLdlXHOVaQKpPV9+aQYBiG1KNePvWZ4PCvukXv+zgLP1SvvqNTQKi2HCV37
RZQd0M0Do9aqrtlDrQLeKE39XZQMBKrype7Vr5JcnXMlaC4A8WFF/+cl11V6m4eY
8GLnoTv+l/G2Hbjwm6/oLPCvrErqd0M+6JK9jXnXHwNr05FpEVqwdYZ5K10bYBBU
wY+oZM8sGrT0Hd0N0PHtcs2eZ6yYLNrAaTvZT3w/sFgEqrDKn6c5WJdKO9PKSvbb
E7whD+WkZPeN2ndh+lGYAEnVzyzVgKmNPOPGFa244QEIUpeZv4d+ivPN9eOwgAVH
l4Ms9+u38VjuIE5LNZCiqOlIzaMBD+dPbOpx7rtEacMs8UgyMVIGPiJcsqzw++Ji
pWHOKRAi82TLLcqt30wgIjCfu49hFPbnfIMCAwEAAaNQME4wHQYDVR0OBBYEFAyp
8Do7nsAhXWAPamH+Vn8ntZ3pMB8GA1UdIwQYMBaAFAyp8Do7nsAhXWAPamH+Vn8n
tZ3pMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAKZcYn3AlK77Yg9K
THZym7Cmr07HA9138PnlMLlVTqJnwAU9nGZ41h/Vth1nZLy9SppefuFwXrmpI5W4
lGqY/t+saEgSzwI8cQ1F6AP0XyeQaGcqNpwHnBNF414Um42Emu+lbBJuYFV53Pgq
nhxGUD7/PbUHSCkTj/LOuvVOeXKMl2muuMpk2lwJnQNAv4mB29F+6mwxntxuSk2O
NFk13QXB5ii1NawT16yW/bYSMZsLQKvz/e/9B+tCjGdbQ8ga3OcEzjGgKRhh4Z/E
gdgl7vtu+ZkE+LHzP9V2lUVlbSP4UhQiZJSEawItSwKlIq/hTjra1kby8UbGMtLp
/vjAznZleF5nG4eQbbM3wDtnpWim0ODXH+p1JNPuK/6GzbWh4eykHQ6JElci26Hg
ug+/7iGAxBcCiPPRX1vT92rnKAIicqzZ1UInlun2X01+pxFo+xGKCYDYAJwyS8Y/
Zy4eFbXJJMGznTef7fNhtBLk6jni90YWBbLgaY62T+y1gYsyfYzQE28Idc7LiKOu
DNEfa4nXAB1zIg9JMKzqbDy4WN0B8tKbBBQIwtRn5DyW7YsmgsgZt6Gs+ochvZdh
5Zlr6T6nvFMunVGNUZP901Xa+HYqCTV7PcrvCDVMOfiNkmrlWf6amFTzL8kBELCC
0O6WGsL6rVCeSVy2BPSna0GzH36g
-----END CERTIFICATE-----


================================================
FILE: testclient.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA1TE9F36zfsSmXbxkfNonYggVA7skrb+10iuLyey5snkOHLsz
EjmL3Gtux02GaqZ8u3GdfeZ/Am2KqFnLq/YiZwYJpwpB54PAtKQqCiIcAPCENZdP
zuya4bWTP+/bLiLdDY0kJhxMrReufBL+xBOFrRhLcsW+tECGsu+d39o7JY7oWnm7
IQYX7cxK1JgaFL5kmUuaoJN0iJhB7V3JIQJMC68Yr5dzrYdzwzc1uxm43Y696HYA
Pkygf41ZoHo5UKWCI9V9M0iz8oUoWrLdlXHOVaQKpPV9+aQYBiG1KNePvWZ4PCvu
kXv+zgLP1SvvqNTQKi2HCV37RZQd0M0Do9aqrtlDrQLeKE39XZQMBKrype7Vr5Jc
nXMlaC4A8WFF/+cl11V6m4eY8GLnoTv+l/G2Hbjwm6/oLPCvrErqd0M+6JK9jXnX
HwNr05FpEVqwdYZ5K10bYBBUwY+oZM8sGrT0Hd0N0PHtcs2eZ6yYLNrAaTvZT3w/
sFgEqrDKn6c5WJdKO9PKSvbbE7whD+WkZPeN2ndh+lGYAEnVzyzVgKmNPOPGFa24
4QEIUpeZv4d+ivPN9eOwgAVHl4Ms9+u38VjuIE5LNZCiqOlIzaMBD+dPbOpx7rtE
acMs8UgyMVIGPiJcsqzw++JipWHOKRAi82TLLcqt30wgIjCfu49hFPbnfIMCAwEA
AQKCAgEAm+JU+Uj7lkXUH9YQ4/nfsh6WvxOnziPPns2YeR1O6uD5IKkAvuK1EYa8
iZ52GqWBrs10iwpu9CeEq3R9KE/g99PCWxF0/wOndG5VDvPB5i33ffgVswfud/t8
n9OSQDndyHrbY8JtjmMygiahgl2D8P1CrblJqCNGWrA6j+PSO7Qy0XURDySVeptW
W/yblW9hv3U4qxEmtHogOp/I4Qn88M4nDr1/J/NTAfrsntJACkDFO6SMqQD+mkWQ
s3arUfyzG+COm2EdsscKqsb+nreIV7aK0fNvGYqSxmj/Pc3gnGzAnb7Bwj8YISqN
LSHjK1/wleaURpUhlc6nvnUppDLiuYC8yB3Xk3XqgEIw96Bn7DLWdXZdCHnm02C1
WiV6SDI6TDY0oNw6qhUZ/Aq/hc4gN+MX/g1TJUXfk2R060Ul4ZY4Ywl89K45cUoo
v7uZM0hkZEukTv1LjlbkMHHMyAoHIVBgjGeIUGUOc3/2zsvRHM5MBhuZQRuTxiGg
vcR9/qdX6I0nZlUpjwLJduWn/rF8v2/lFABB36n9frK5wm6HeFp1hEitTljaeRMA
s/RgM9XiHdZpzJT1YK6J6dXzJuUYCiSAeKps4n7xT4F66FTQuXMLqD2eLkiZIsEb
nx6m0muEav+fUg8a/xi4WP/2eMNg7Ayq/PYtY76wIoxsowA7AQECggEBAPT09yFV
wrxV2K3bFgC2+0sMcwoR984j8X82aipMHPKxXPiNE0budIP2OWhMOoIelrR3Pjdm
xizU0bGI3lo5po9t22MhoQIrEREpv5rrUR+sg4DFUPUATRWlVGdr/EdsElQKTKKi
ZpCqrBAMd8D6Vp3EH+LGpy1i37MoY8T+ljZ9wMStsKgN0/k6vHzwDVyUwadvrzNO
v3HuEP+XRRxC/fhuxlti2/AS4Tm9IJIwUzNaVBpio8mLiDWdwHlGZXzxP6vlE3ef
xzHJ71D89qFlxr4EDjiKzYxiiZbGqowv2NDS+Z87rYD5mFCaVUtzrj9PJHLNTh11
P56QizutmAEvazkCggEBAN7NriGkK1+Tkf6hc5KPBgqZaUDSDAHr8DBpSMJy7sTq
yOmd1L+09rNqAXy0wV6isU9v3wYMB38xIoxYMkkW1HQr6tWpcyzbC9HomBKMZGoX
jNmj9MWKlaDN8cXfxx7l7CAf/1oJs7MAFMVu/oKnK35+uZoo9mvo0G6udc7vRHGa
OLPoySq7RuAWT7z9ULd1+Ny+Y4kvWsO/hdjVU4a9r9KQf/eNXt5PWmMb69v7hNGD
2j2CqxdlnRYdUW6y3d8919lNneTXZBbzovw6aK58l124sYjuMyDIoof5ErouWDps
SM1gE+xo48M1cSuZrv+RJu5G6mmtDe1/aXFzeEaBGZsCggEBAKB6R9kf7TcjapPj
nxOSzSjKnCcxxE3ZgGIeDQlu2dwpVEZFbiafG9hEHDH3FrGeRo8uO6ViAFzohAQy
LbGgaT039G2KX4gjHMhIuI1OstP0WiannjUUIGwY5yXmOd20sIE8Sh6WFGmcVqMg
9+eGWe57yYPxLx7t0q31vP8W5uQGGJ8BR2WhwYha8ZdMUQShNAl0gqwzX/rMw3ge
6xjrzqTONcczCfHK/KCuBcOgQzG2cLjkfHcSoYa2tZz+AIkNJ/B+X/WTyJUWvWEq
iI0ON1jPIV3rmWPqPkd4Gc1Dn2CXhw/Jsg539lB/+3c17ybsu202kYF9CdPg0Eal
oJrOLQkCggEADS1U8yBmgEyWAd1CnJRg4xeXpgHGPAbcOcDAUN/DR1orb8Wp43ys
aogGdn2qQhKVMgGHyy/C8b7SMEK3FqOHBSfjx6cx7KE33b5H4DD1b2DdL7IGs/gy
SURk3DMT77vhbzT1QTn5qsiCcfrSip+gbubHy1pI2LD4QtOGnCqCfcWFPP6zhxd0
ZaRsKt1AfNk5UrTf5ikq0RDutZhITFvDnkx1hQqTZcqDqgDoviXuAQYvThwASm30
EG7DdiyV+rIJpgx1Hieu/7yBEzHRJyCvQxe9SD/uPi4fjrMobGJ5TVtCIwNfqke5
0L3EZ7O7KdpH1yfSjVVy0W0Lq24M2v6fqQKCAQA25Xsu2HVCcE3KsEuywGJOs3hA
kXPPJu7vyLRPDPlbDhVMtKSUOAGEHyh8h6o65VZF4322gZ++AoV+EFWlGfQYVYGu
+/uBeTf6y4IuCPvCtsELiERtMAMbrhwSDB83/xIMcKkvs7X7DQ8GRe6n3mDZa6cg
EjZhQRnxnKDR7AO8pM19GuMVPDeMVNdgfUJTSDO7nifuiPEO9rtAwuH/y+RNeLIJ
9a/1zcHXU1uCJPxITlN3ckhWIVEw7ycQ0xXULt4UfcfPHNtfMR0ccUYlP6zI1eBc
CS5K58CWPWjBSiS+SFUIIx6qPtjBDuYBcqnrWqekd7m4yYKOJIaUbXhwm4IN
-----END RSA PRIVATE KEY-----


================================================
FILE: testserver.cert
================================================
-----BEGIN CERTIFICATE-----
MIIFITCCAwmgAwIBAgIJALu5MYN2H+2PMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV
BAMMG0JvbWJhcmRpZXIgU2VydmVyIFRlc3QgQ2VydDAgFw0xNzAxMjYwNjM2NTRa
GA8zMDE2MDUyOTA2MzY1NFowJjEkMCIGA1UEAwwbQm9tYmFyZGllciBTZXJ2ZXIg
VGVzdCBDZXJ0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1laXP9o5
eo9YFOdqK/Il1f4AePr7UZ/mq5nbBkA3Pt/5uW1LIq0ECJ3+JzGrzIkYjrj6an0c
3bV5GDW/XA6yH4iMV36ADc+D+SWixQQkkWNrIL3nTHnnxOFbml4uCCDEV5TGWv8Z
GNraUPPSIZAM/OO4uKTMHKiacpnGZ8ZqUuJ0E0F3OHoLwxpJsAhiKv18iy7mHvva
PBCQ/aq8LYUOLRwiL019EVx+LFuMj9Ugc9G5lYHnn0jI/AcMdWeXil01aFEFtGpG
S7b4FV5d3x1C8l1PDDWQfuBzu4wrztnCUnvQyUTZAmBJFXt2V5MxO6p69qvGav//
IdPTxToeaz0oil+O+7cJ4gqjUBsr8xPuGLYjytZ2KJEqTIJJogXFylpwDWIvbXBw
pFhDsuE9J2oz9gPC8R54lb93cuNAeGrWZfoJ8qSzOsO4CKgasdTTrv3uFTgnovh4
qJ4SaRjOtiSmWLZhy7fSG86kB+ZuGkEOUJiFok2aNJL1UI10QuSc095JLFnqZ3LP
DG3gkdwqmYMYFnSKv43Z+azO1+t5BUSNyddSb2ZEF4d5J/UzN/D5XAxgJJoyfMK7
vHfOlglnfBkSdyxw1/BbOPLBFVDWinV33sabtgEismgdDaYjActBuRdl1md4BPRJ
Z714Noquv7qcOYXNuqdhLNWjWh4d0HwA0yECAwEAAaNQME4wHQYDVR0OBBYEFB0Y
it+Qidk1AgL+N00nyYDWgeEFMB8GA1UdIwQYMBaAFB0Yit+Qidk1AgL+N00nyYDW
geEFMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBABjxo7L4N+3lsHya
14dk8ycssuHkJvE2PhbandywV0E5bfqkoVGOmSVw50BIkpQ3WMTwACJZsjeTF1OJ
CtDHSx3EJ3wPOMChAA06QdWSwG6Gxi6NYLrZ+60VjX3f64zwvmasD2xHT20icqAR
H/405qzv5MyELvDzD9u+agdIvf2yXGywGiT8p1yPQZX5pn/1omWZEMSNICOzhFqv
U6FfYOGu/g+FJYmV5JRitMBNcr1sTJI0eHEYWW/d7yxBCtmoN4UoH1TZepijb65n
CT9xl9WX4CRGiG6/T4wcYmL2Q8OwjEn/JNUCSxU9tcyJr66JIIx4BUhUJ012BPXu
BbohBaNA8ygMpt1sweBteVCC5O/O1J7YKYG1o0J2Fd4DLol+bWfM8IijoT23XYF3
I5fcf/Y61iAxx9PqCe7BsRsPi7WXrPxZJlfocXIWbdwVwpQUngjQjiVgdcPH7LnB
NI1E2PDcDVJDVsS7XB/zK2nyY31DlQ8QZYpOzIeHjy4UcepClzk8JqeIQEmh01S7
p7fIIt51N2s2TpvZGC/wGL/0iFn/4mwyXvH9RZn8AUGBWrdB/kY/DD+Nmz8tHtlH
PpM4uDdRwh+Ks62Rw1qde5xIZ82PjLxJ+P79rQ+wYejmfenLr1PPnl1q0W/MT8gT
Puxw1k5iEsR8O9CjyEYP6bhQoZfb
-----END CERTIFICATE-----


================================================
FILE: testserver.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEA1laXP9o5eo9YFOdqK/Il1f4AePr7UZ/mq5nbBkA3Pt/5uW1L
Iq0ECJ3+JzGrzIkYjrj6an0c3bV5GDW/XA6yH4iMV36ADc+D+SWixQQkkWNrIL3n
THnnxOFbml4uCCDEV5TGWv8ZGNraUPPSIZAM/OO4uKTMHKiacpnGZ8ZqUuJ0E0F3
OHoLwxpJsAhiKv18iy7mHvvaPBCQ/aq8LYUOLRwiL019EVx+LFuMj9Ugc9G5lYHn
n0jI/AcMdWeXil01aFEFtGpGS7b4FV5d3x1C8l1PDDWQfuBzu4wrztnCUnvQyUTZ
AmBJFXt2V5MxO6p69qvGav//IdPTxToeaz0oil+O+7cJ4gqjUBsr8xPuGLYjytZ2
KJEqTIJJogXFylpwDWIvbXBwpFhDsuE9J2oz9gPC8R54lb93cuNAeGrWZfoJ8qSz
OsO4CKgasdTTrv3uFTgnovh4qJ4SaRjOtiSmWLZhy7fSG86kB+ZuGkEOUJiFok2a
NJL1UI10QuSc095JLFnqZ3LPDG3gkdwqmYMYFnSKv43Z+azO1+t5BUSNyddSb2ZE
F4d5J/UzN/D5XAxgJJoyfMK7vHfOlglnfBkSdyxw1/BbOPLBFVDWinV33sabtgEi
smgdDaYjActBuRdl1md4BPRJZ714Noquv7qcOYXNuqdhLNWjWh4d0HwA0yECAwEA
AQKCAgEAmRXPgTODyh2Hc6a1Fh4lF+oKvF3GEk56miWRYa2Lx8SAwAdnmqSoNN9j
HutDIRrqB0Xm1Rf2/gMXMktxGXcFkbAdTIB1RWfpgpF25/BFjfHMGd6IzP5koyGy
I1cQ2Y1Nrp/77BI3AqGNPDRo6L/SBu0+ieJqRi3F4gQiyQvV9Mz4yqf/Vr8Ul4y3
BJt4Qew6f85HXenTvQK4C/Vd4cUekul9IPvfT/8XvubERhaazx4Dxty5afK6WgdO
xqvueEyKUK9Nu8YL3xgXqGt18F0d66zpQHchdP0qq9E5mMu/FtqIDLi3phLPICDG
LVZb25mvqW6WkOW2e5qnrj4Ma9uKj7q8Pj3Xh69asZaHx71K2Stc7cgEFJxjm0jm
mDZd1huCVwokpWr1hVj353kS/VK85qfN0YoBp1Lz+mi7vqQ+I+o6+IXQblwI0hAA
zYJ91ixGM4Krgc0Jj0g+pIY9AYbESxyvT/wnc2Ub1Fi4e7qvmeoWCSa/gUo6fcro
VlWIcVFj1itNVhvFm29v8aypbpWJepMHPPNAm97bhvjzIiXQMh8ET7cdmcLiKUK7
n1GVEld8UlNThmQ54/d6vSK7+aRtqq/ImVyGZdl7SW8RvVnI/AUQYQXirNdft7Lb
G6HGbLsfk4f/xznKNJt1vGgYpg/8YB1D2allhVClvtA+XbjBYWECggEBAPrOLcsE
y7IbsppDdzNNXSMcgay+24ZD9BEEwd6wAKV4uAc5Hq3JUaene/K8g87g+Vrkx7SS
mDfKUOvepYwflVBwM40Mo8V/zif0riqhYbrGPFgEw6cfiGvW34WUb9KOmrP96hEl
pEQEDetkjjeaiRTWTeTU/Hy6CLXz+4aEfCXib+vKuat8c2E3AXokaTGHsl6BKuEM
GlYod4tc62bcObId8BcwjqS+hsjAD6tWYkeeF6L4iq7rZ1XmHFUD281ZSyW2+BJL
ieSM84ZSg3hXZyTEUPvHKn4QQmNzrkm06VBmvHWDU7avFqfrvOLOcDqPBzpnxAUA
15oKlujae4PVSh8CggEBANrHDkWB4oxSDmFPk27EkzMs83eWJ4sjpT+ZUC1wo4v3
icp4Cc62oow4+tpoA8novx3tSwRfnfKig9w4svlVa2piHv6//NN6LM0jckx1dZdb
TVeAFgGchpzTpCZ+F+vw4Qzgrw2r/Pa+ShyR0FyyRwu2wAQXNl74815b01iAWa3f
UoIkvxlLu6Fy0pmeFQWyFI/WIIpXCyNrDxnrCWjcF28+et7+yngUvQ+4lRn9nF/T
oSjl4uV6RNlI7Q1KI0pqHWIFYuFYFRavgAXIshaZrPpfskE8RrX1b1IofLHi1q6P
A1O55Gxd/WWl5YTu+lpd4HeRPb+QbL0AgRujmZ98ur8CggEAe9Ts2z5k7G2sg2oo
IpZiFAHxLL+XV/WZPgXhSvgPeaPfCQH02c16mZKiKjlVwwFlXLF0wP1YVsN3rN3j
UwoNCQg9C7lf6xWtTiELFVVVEYjrJnJDv/JbwxL2jde6VnW+gHwv44N4VXTDAqRF
a8LLSBR/pSpb96FKx7vNRp+HRJVGuV8AyWDK/wbPneT4Y1IiiXKxHyiAoGWekJqy
R7kYa49IicqZw1Gm7tuVYP1nzQCLnxWkM7Va8hiJiJg9IGikJ9ztIutVDBlj68A1
1WciMA8WBRpTKqcQgFYPiajfQalYB5Vt8dcFEqfcPQe8dc1EvluZdvbxfMcZt6KY
NYFL9QKCAQEAyl7C9sy0kPP+VUlUqWuwdfAorf/5SB2K6A+bOM0um3Q4w07SU6Jh
LbAvawQ4LPbcgoRTlhIUerKVoonYFAdNuzRUU3WoGr6y3nbhbZRhV8ae/kd/E7KE
WmDzQJ/25MsGgfD8PHtRHbTbvR2sTXKjgVRkvePy6VsDU89A6mafjdQ78CKpmm6R
e0BJSswNyhz2JC8AHrdxmCuZ5nGhXJvqGX8EDW5GP1l/oSEu2sHbelC6jKhJf9fg
A9YPYPGpP1Z1I4yz8JqXt0pT9AW3pmw0s8z9iJaHGh2UAb1tyuZ3izTC8RnND+jJ
UtNoQdUFQ73+uttg8OhZjWMACl8E5aBs5QKCAQEAjBAMw41WxOPbF3S9zLOU2mqq
T+sUKhhv36Ri9nKOtyfbOWuMgtYZHDijAkkP5yXDda31kgPkx3qJH4K6F6l+1PUF
w+rbvUck15Vfed0BrzUxXL9m4JAq59TnAWajmoI/5eaeyY6M8Vfz4XQwYNNm353h
zTBbxxO9AKz9I8/mRnctGo9UZNhhBwh30t8oQl2hWLqC1CuY/1R71tkxy6Qz1Tg+
wcnDU8IbyWtuLKBikBOLzTb+EvSMfEZyS9bLUhUZvWznvwJgaLCON+k5gQSjwE98
nvR8VhuFDyq09ChAmFCAOA3plCOHCa/yYEX1BYx0tbP/yOYSePC6sKF1UzLBrw==
-----END RSA PRIVATE KEY-----
Download .txt
gitextract_k5a5acgj/

├── .github/
│   ├── ISSUE_TEMPLATE.md
│   └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .semaphore/
│   └── semaphore.yml
├── LICENSE
├── README.md
├── args_parser.go
├── args_parser_test.go
├── bombardier.go
├── bombardier_performance_test.go
├── bombardier_test.go
├── build.py
├── client_cert.go
├── client_cert_test.go
├── clients.go
├── clients_test.go
├── cmd/
│   └── utils/
│       └── simplebenchserver/
│           ├── doc.go
│           └── main.go
├── common.go
├── completion_barriers.go
├── completion_barriers_test.go
├── config.go
├── config_test.go
├── dialer.go
├── doc.go
├── docs/
│   └── CONTRIBUTING.md
├── error_map.go
├── error_map_test.go
├── flags.go
├── flags_test.go
├── format.go
├── format_test.go
├── go.mod
├── go.sum
├── headers.go
├── headers_test.go
├── internal/
│   └── test_info.go
├── limiter.go
├── limiter_barrier_test.go
├── limiter_test.go
├── proxy_reader.go
├── rateestimator.go
├── rateestimator_test.go
├── template/
│   └── doc.go
├── templates.go
├── testbody.txt
├── testclient.cert
├── testclient.key
├── testserver.cert
└── testserver.key
Download .txt
SYMBOL INDEX (258 symbols across 32 files)

FILE: args_parser.go
  type argsParser (line 14) | type argsParser interface
  type kingpinParser (line 18) | type kingpinParser struct
    method parse (line 187) | func (k *kingpinParser) parse(args []string) (config, error) {
  function newKingpinParser (line 46) | func newKingpinParser() argsParser {
  function parsePrintSpec (line 238) | func parsePrintSpec(spec string) (bool, bool, bool, error) {

FILE: args_parser_test.go
  constant programName (line 12) | programName = "bombardier"
  function TestInvalidArgsParsing (line 15) | func TestInvalidArgsParsing(t *testing.T) {
  function TestUnspecifiedArgParsing (line 38) | func TestUnspecifiedArgParsing(t *testing.T) {
  function TestArgsParsing (line 47) | func TestArgsParsing(t *testing.T) {
  function TestParsePrintSpec (line 732) | func TestParsePrintSpec(t *testing.T) {
  function TestArgsParsingWithEmptyPrintSpec (line 796) | func TestArgsParsingWithEmptyPrintSpec(t *testing.T) {
  function TestArgsParsingWithInvalidPrintSpec (line 808) | func TestArgsParsingWithInvalidPrintSpec(t *testing.T) {
  function TestEmbeddedURLParsing (line 826) | func TestEmbeddedURLParsing(t *testing.T) {

FILE: bombardier.go
  type bombardier (line 23) | type bombardier struct
    method prepareTemplate (line 178) | func (b *bombardier) prepareTemplate() (*template.Template, error) {
    method writeStatistics (line 226) | func (b *bombardier) writeStatistics(
    method performSingleRequest (line 251) | func (b *bombardier) performSingleRequest() {
    method worker (line 259) | func (b *bombardier) worker() {
    method barUpdater (line 270) | func (b *bombardier) barUpdater() {
    method rateMeter (line 292) | func (b *bombardier) rateMeter() {
    method recordRps (line 315) | func (b *bombardier) recordRps() {
    method bombard (line 327) | func (b *bombardier) bombard() {
    method printIntro (line 348) | func (b *bombardier) printIntro() {
    method gatherInfo (line 359) | func (b *bombardier) gatherInfo() internal.TestInfo {
    method printStats (line 425) | func (b *bombardier) printStats() {
    method redirectOutputTo (line 433) | func (b *bombardier) redirectOutputTo(out io.Writer) {
    method disableOutput (line 438) | func (b *bombardier) disableOutput() {
  function newBombardier (line 62) | func newBombardier(c config) (*bombardier, error) {
  function makeHTTPClient (line 162) | func makeHTTPClient(clientType clientTyp, cc *clientOpts) client {
  function main (line 443) | func main() {

FILE: bombardier_performance_test.go
  function BenchmarkBombardierSingleReqPerf (line 21) | func BenchmarkBombardierSingleReqPerf(b *testing.B) {
  function BenchmarkBombardierRateLimitPerf (line 38) | func BenchmarkBombardierRateLimitPerf(b *testing.B) {
  function benchmarkFireRequest (line 56) | func benchmarkFireRequest(c config, bm *testing.B) {

FILE: bombardier_test.go
  function TestBombardierShouldFireSpecifiedNumberOfRequests (line 20) | func TestBombardierShouldFireSpecifiedNumberOfRequests(t *testing.T) {
  function testBombardierShouldFireSpecifiedNumberOfRequests (line 24) | func testBombardierShouldFireSpecifiedNumberOfRequests(
  function TestBombardierShouldFinish (line 57) | func TestBombardierShouldFinish(t *testing.T) {
  function testBombardierShouldFinish (line 61) | func testBombardierShouldFinish(clientType clientTyp, t *testing.T) {
  function TestBombardierShouldSendHeaders (line 102) | func TestBombardierShouldSendHeaders(t *testing.T) {
  function testBombardierShouldSendHeaders (line 106) | func testBombardierShouldSendHeaders(clientType clientTyp, t *testing.T) {
  function TestBombardierHTTPCodeRecording (line 152) | func TestBombardierHTTPCodeRecording(t *testing.T) {
  function testBombardierHTTPCodeRecording (line 156) | func testBombardierHTTPCodeRecording(clientType clientTyp, t *testing.T) {
  function TestBombardierTimeoutRecoding (line 215) | func TestBombardierTimeoutRecoding(t *testing.T) {
  function testBombardierTimeoutRecoding (line 219) | func testBombardierTimeoutRecoding(clientType clientTyp, t *testing.T) {
  function TestBombardierThroughputRecording (line 250) | func TestBombardierThroughputRecording(t *testing.T) {
  function testBombardierThroughputRecording (line 254) | func testBombardierThroughputRecording(clientType clientTyp, t *testing....
  function TestBombardierStatsPrinting (line 288) | func TestBombardierStatsPrinting(t *testing.T) {
  function TestBombardierErrorIfFailToReadClientCert (line 334) | func TestBombardierErrorIfFailToReadClientCert(t *testing.T) {
  function TestBombardierClientCerts (line 354) | func TestBombardierClientCerts(t *testing.T) {
  function testBombardierClientCerts (line 358) | func testBombardierClientCerts(clientType clientTyp, t *testing.T) {
  function TestBombardierRateLimiting (line 431) | func TestBombardierRateLimiting(t *testing.T) {
  function testBombardierRateLimiting (line 435) | func testBombardierRateLimiting(clientType clientTyp, t *testing.T) {
  function testAllClients (line 473) | func testAllClients(parent *testing.T, testFun func(clientTyp, *testing....
  function TestBombardierSendsBody (line 482) | func TestBombardierSendsBody(t *testing.T) {
  function testBombardierSendsBody (line 486) | func testBombardierSendsBody(clientType clientTyp, t *testing.T) {
  function TestBombardierSendsBodyFromFile (line 526) | func TestBombardierSendsBodyFromFile(t *testing.T) {
  function testBombardierSendsBodyFromFile (line 530) | func testBombardierSendsBodyFromFile(clientType clientTyp, t *testing.T) {
  function TestBombardierFileDoesntExist (line 575) | func TestBombardierFileDoesntExist(t *testing.T) {
  function TestBombardierStreamsBody (line 592) | func TestBombardierStreamsBody(t *testing.T) {
  function testBombardierStreamsBody (line 596) | func testBombardierStreamsBody(clientType clientTyp, t *testing.T) {
  function TestBombardierStreamsBodyFromFile (line 640) | func TestBombardierStreamsBodyFromFile(t *testing.T) {
  function testBombardierStreamsBodyFromFile (line 644) | func testBombardierStreamsBodyFromFile(clientType clientTyp, t *testing....
  function TestBombardierShouldSendCustomHostHeader (line 693) | func TestBombardierShouldSendCustomHostHeader(t *testing.T) {
  function testBombardierShouldSendCustomHostHeader (line 697) | func testBombardierShouldSendCustomHostHeader(

FILE: client_cert.go
  function readClientCert (line 9) | func readClientCert(certPath, keyPath string) ([]tls.Certificate, error) {
  function generateTLSConfig (line 17) | func generateTLSConfig(c config) (*tls.Config, error) {

FILE: client_cert_test.go
  function TestGenerateTLSConfig (line 7) | func TestGenerateTLSConfig(t *testing.T) {

FILE: clients.go
  type client (line 15) | type client interface
  type bodyStreamProducer (line 19) | type bodyStreamProducer
  type clientOpts (line 21) | type clientOpts struct
  type fasthttpClient (line 39) | type fasthttpClient struct
    method do (line 78) | func (c *fasthttpClient) do() (
  function newFastHTTPClient (line 50) | func newFastHTTPClient(opts *clientOpts) client {
  type httpClient (line 117) | type httpClient struct
    method do (line 154) | func (c *httpClient) do() (
  function newHTTPClient (line 128) | func newHTTPClient(opts *clientOpts) client {
  function headersToFastHTTPHeaders (line 200) | func headersToFastHTTPHeaders(h *headersList) *fasthttp.RequestHeader {
  function headersToHTTPHeaders (line 211) | func headersToHTTPHeaders(h *headersList) http.Header {

FILE: clients_test.go
  function TestShouldReturnNilIfNoHeadersWhereSet (line 14) | func TestShouldReturnNilIfNoHeadersWhereSet(t *testing.T) {
  function TestShouldReturnEmptyHeadersIfNoHeaadersWhereSet (line 21) | func TestShouldReturnEmptyHeadersIfNoHeaadersWhereSet(t *testing.T) {
  function TestShouldProperlyConvertToHttpHeaders (line 28) | func TestShouldProperlyConvertToHttpHeaders(t *testing.T) {
  function TestHTTP2Client (line 60) | func TestHTTP2Client(t *testing.T) {
  function TestHTTP1Clients (line 117) | func TestHTTP1Clients(t *testing.T) {

FILE: cmd/utils/simplebenchserver/main.go
  function main (line 24) | func main() {

FILE: common.go
  constant decBase (line 13) | decBase = 10
  constant rateLimitInterval (line 15) | rateLimitInterval = 10 * time.Millisecond
  constant oneSecond (line 16) | oneSecond         = 1 * time.Second
  constant exitFailure (line 18) | exitFailure = 1
  function ParseURLOrPanic (line 61) | func ParseURLOrPanic(s string) *url.URL {
  function init (line 69) | func init() {

FILE: completion_barriers.go
  type completionBarrier (line 9) | type completionBarrier interface
  type countingCompletionBarrier (line 17) | type countingCompletionBarrier struct
    method tryGrabWork (line 30) | func (c *countingCompletionBarrier) tryGrabWork() bool {
    method jobDone (line 40) | func (c *countingCompletionBarrier) jobDone() {
    method done (line 49) | func (c *countingCompletionBarrier) done() <-chan struct{} {
    method cancel (line 53) | func (c *countingCompletionBarrier) cancel() {
    method completed (line 59) | func (c *countingCompletionBarrier) completed() float64 {
  function newCountingCompletionBarrier (line 23) | func newCountingCompletionBarrier(numReqs uint64) completionBarrier {
  type timedCompletionBarrier (line 69) | type timedCompletionBarrier struct
    method tryGrabWork (line 94) | func (c *timedCompletionBarrier) tryGrabWork() bool {
    method jobDone (line 103) | func (c *timedCompletionBarrier) jobDone() {
    method done (line 106) | func (c *timedCompletionBarrier) done() <-chan struct{} {
    method cancel (line 110) | func (c *timedCompletionBarrier) cancel() {
    method completed (line 116) | func (c *timedCompletionBarrier) completed() float64 {
  function newTimedCompletionBarrier (line 76) | func newTimedCompletionBarrier(duration time.Duration) completionBarrier {

FILE: completion_barriers_test.go
  function TestCouintingCompletionBarrierWait (line 9) | func TestCouintingCompletionBarrierWait(t *testing.T) {
  function TestTimedCompletionBarrierWait (line 32) | func TestTimedCompletionBarrierWait(t *testing.T) {
  function TestTimeBarrierCancel (line 63) | func TestTimeBarrierCancel(t *testing.T) {
  function TestCountedBarrierCancel (line 80) | func TestCountedBarrierCancel(t *testing.T) {
  function TestTimeBarrierPanicOnBadDuration (line 105) | func TestTimeBarrierPanicOnBadDuration(t *testing.T) {
  function approximatelyEqual (line 118) | func approximatelyEqual(expected, actual, err time.Duration) bool {

FILE: config.go
  type config (line 10) | type config struct
    method checkArgs (line 50) | func (c *config) checkArgs() error {
    method checkOrSetDefaultTestType (line 71) | func (c *config) checkOrSetDefaultTestType() {
    method testType (line 77) | func (c *config) testType() testTyp {
    method checkURL (line 87) | func (c *config) checkURL() error {
    method checkRate (line 94) | func (c *config) checkRate() error {
    method checkRunParameters (line 101) | func (c *config) checkRunParameters() error {
    method checkTimeoutDuration (line 114) | func (c *config) checkTimeoutDuration() error {
    method checkHTTPParameters (line 121) | func (c *config) checkHTTPParameters() error {
    method checkCertPaths (line 134) | func (c *config) checkCertPaths() error {
    method timeoutMillis (line 143) | func (c *config) timeoutMillis() uint64 {
  type testTyp (line 34) | type testTyp
  constant none (line 37) | none testTyp = iota
  constant timed (line 38) | timed
  constant counted (line 39) | counted
  type invalidHTTPMethodError (line 42) | type invalidHTTPMethodError struct
    method Error (line 46) | func (i *invalidHTTPMethodError) Error() string {
  function allowedHTTPMethod (line 147) | func allowedHTTPMethod(method string) bool {
  function canHaveBody (line 152) | func canHaveBody(method string) bool {
  type clientTyp (line 157) | type clientTyp
    method String (line 165) | func (ct clientTyp) String() string {
  constant fhttp (line 160) | fhttp clientTyp = iota
  constant nhttp1 (line 161) | nhttp1
  constant nhttp2 (line 162) | nhttp2

FILE: config_test.go
  function TestCanHaveBody (line 12) | func TestCanHaveBody(t *testing.T) {
  function TestAllowedHttpMethod (line 31) | func TestAllowedHttpMethod(t *testing.T) {
  function TestCheckArgs (line 52) | func TestCheckArgs(t *testing.T) {
  function TestCheckArgsUnsupportedURLScheme (line 262) | func TestCheckArgsUnsupportedURLScheme(t *testing.T) {
  function TestCheckArgsInvalidRequestMethod (line 278) | func TestCheckArgsInvalidRequestMethod(t *testing.T) {
  function TestCheckArgsTestType (line 298) | func TestCheckArgsTestType(t *testing.T) {
  function TestTimeoutMillis (line 358) | func TestTimeoutMillis(t *testing.T) {
  function TestInvalidHTTPMethodError (line 374) | func TestInvalidHTTPMethodError(t *testing.T) {
  function TestClientTypToStringConversion (line 383) | func TestClientTypToStringConversion(t *testing.T) {
  function clientTypeFromString (line 401) | func clientTypeFromString(s string) clientTyp {

FILE: dialer.go
  type countingConn (line 10) | type countingConn struct
    method Read (line 15) | func (cc *countingConn) Read(b []byte) (n int, err error) {
    method Write (line 25) | func (cc *countingConn) Write(b []byte) (n int, err error) {

FILE: error_map.go
  type errorMap (line 10) | type errorMap struct
    method add (line 21) | func (e *errorMap) add(err error) {
    method get (line 38) | func (e *errorMap) get(err error) uint64 {
    method sum (line 49) | func (e *errorMap) sum() uint64 {
    method byFrequency (line 83) | func (e *errorMap) byFrequency() errorsByFrequency {
  function newErrorMap (line 15) | func newErrorMap() *errorMap {
  type errorWithCount (line 59) | type errorWithCount struct
    method String (line 64) | func (ewc *errorWithCount) String() string {
  type errorsByFrequency (line 69) | type errorsByFrequency
    method Len (line 71) | func (ebf errorsByFrequency) Len() int {
    method Less (line 75) | func (ebf errorsByFrequency) Less(i, j int) bool {
    method Swap (line 79) | func (ebf errorsByFrequency) Swap(i, j int) {

FILE: error_map_test.go
  function TestErrorMapAdd (line 9) | func TestErrorMapAdd(t *testing.T) {
  function TestErrorMapGet (line 18) | func TestErrorMapGet(t *testing.T) {
  function TestByFrequency (line 26) | func TestByFrequency(t *testing.T) {
  function TestErrorWithCountToStringConversion (line 49) | func TestErrorWithCountToStringConversion(t *testing.T) {
  function BenchmarkErrorMapAdd (line 59) | func BenchmarkErrorMapAdd(b *testing.B) {
  function BenchmarkErrorMapGet (line 70) | func BenchmarkErrorMapGet(b *testing.B) {

FILE: flags.go
  constant nilStr (line 9) | nilStr = "nil"
  type nullableUint64 (line 12) | type nullableUint64 struct
    method String (line 16) | func (n *nullableUint64) String() string {
    method Set (line 23) | func (n *nullableUint64) Set(value string) error {
  type nullableDuration (line 33) | type nullableDuration struct
    method String (line 37) | func (n *nullableDuration) String() string {
    method Set (line 44) | func (n *nullableDuration) Set(value string) error {
  type nullableString (line 53) | type nullableString struct
    method String (line 57) | func (n *nullableString) String() string {
    method Set (line 64) | func (n *nullableString) Set(value string) error {

FILE: flags_test.go
  function TestNullableUint64ConversionToString (line 11) | func TestNullableUint64ConversionToString(t *testing.T) {
  function TestNullableUint64Parsing (line 23) | func TestNullableUint64Parsing(t *testing.T) {
  function TestNullableDurationConversionToString (line 43) | func TestNullableDurationConversionToString(t *testing.T) {
  function TestNullableDurationParsing (line 55) | func TestNullableDurationParsing(t *testing.T) {
  function TestNullableStringConversionToString (line 68) | func TestNullableStringConversionToString(t *testing.T) {

FILE: format.go
  type units (line 7) | type units struct
  function formatUnits (line 31) | func formatUnits(n float64, m *units, prec int) string {
  function formatBinary (line 44) | func formatBinary(n float64) string {
  function formatTimeUs (line 48) | func formatTimeUs(n float64) string {

FILE: format_test.go
  constant KB (line 8) | KB = 1024
  constant MB (line 9) | MB = KB * 1024
  constant GB (line 10) | GB = MB * 1024
  constant K (line 12) | K = 1000
  constant M (line 13) | M = K * 1000
  function TestShouldFormatBinary (line 16) | func TestShouldFormatBinary(t *testing.T) {
  function TestShouldFormatUs (line 45) | func TestShouldFormatUs(t *testing.T) {

FILE: headers.go
  type header (line 8) | type header struct
  type headersList (line 12) | type headersList
    method String (line 14) | func (h *headersList) String() string {
    method IsCumulative (line 18) | func (h *headersList) IsCumulative() bool {
    method Set (line 22) | func (h *headersList) Set(value string) error {

FILE: headers_test.go
  function TestHeadersToStringConversion (line 7) | func TestHeadersToStringConversion(t *testing.T) {
  function TestShouldErrorOnInvalidFormat (line 32) | func TestShouldErrorOnInvalidFormat(t *testing.T) {
  function TestShouldProperlyAddValidHeaders (line 39) | func TestShouldProperlyAddValidHeaders(t *testing.T) {
  function TestShouldTrimHeaderValues (line 54) | func TestShouldTrimHeaderValues(t *testing.T) {

FILE: internal/test_info.go
  type TestInfo (line 12) | type TestInfo struct
  type Header (line 18) | type Header struct
  type Spec (line 23) | type Spec struct
    method RequestURL (line 49) | func (s Spec) RequestURL() string {
    method IsTimedTest (line 54) | func (s Spec) IsTimedTest() bool {
    method IsTestWithNumberOfReqs (line 60) | func (s Spec) IsTestWithNumberOfReqs() bool {
    method IsFastHTTP (line 66) | func (s Spec) IsFastHTTP() bool {
    method IsNetHTTPV1 (line 72) | func (s Spec) IsNetHTTPV1() bool {
    method IsNetHTTPV2 (line 78) | func (s Spec) IsNetHTTPV2() bool {
  type Results (line 83) | type Results struct
    method Throughput (line 112) | func (r Results) Throughput() float64 {
    method LatenciesStats (line 129) | func (r Results) LatenciesStats(percentiles []float64) *LatenciesStats {
    method RequestsStats (line 207) | func (r Results) RequestsStats(percentiles []float64) *RequestsStats {
  type ReadonlyUint64Histogram (line 97) | type ReadonlyUint64Histogram interface
  type ReadonlyFloat64Histogram (line 104) | type ReadonlyFloat64Histogram interface
  type LatenciesStats (line 117) | type LatenciesStats struct
  type RequestsStats (line 195) | type RequestsStats struct
  type ErrorWithCount (line 287) | type ErrorWithCount struct
  type TestType (line 293) | type TestType
  constant _ (line 296) | _ TestType = iota
  constant ByTime (line 298) | ByTime
  constant ByNumberOfReqs (line 301) | ByNumberOfReqs
  type ClientType (line 305) | type ClientType
  constant FastHTTP (line 309) | FastHTTP ClientType = iota
  constant NetHTTP1 (line 311) | NetHTTP1
  constant NetHTTP2 (line 313) | NetHTTP2

FILE: limiter.go
  type token (line 11) | type token
  constant brk (line 14) | brk token = iota
  constant cont (line 15) | cont
  type limiter (line 18) | type limiter interface
  type nooplimiter (line 22) | type nooplimiter struct
    method pace (line 24) | func (n *nooplimiter) pace(<-chan struct{}) token {
  type bucketlimiter (line 28) | type bucketlimiter struct
    method pace (line 47) | func (b *bucketlimiter) pace(done <-chan struct{}) (res token) {
  function newBucketLimiter (line 33) | func newBucketLimiter(rate uint64) limiter {

FILE: limiter_barrier_test.go
  function TestNoopLimiterCounterBarrierCombination (line 9) | func TestNoopLimiterCounterBarrierCombination(t *testing.T) {
  function TestBucketLimiterCounterBarrierCombination (line 38) | func TestBucketLimiterCounterBarrierCombination(t *testing.T) {

FILE: limiter_test.go
  constant maxRps (line 11) | maxRps = 10000000
  function TestNoopLimiter (line 13) | func TestNoopLimiter(t *testing.T) {
  function BenchmarkNoopLimiter (line 44) | func BenchmarkNoopLimiter(bm *testing.B) {
  function TestBucketLimiterLowRates (line 56) | func TestBucketLimiterLowRates(t *testing.T) {
  function TestBucketLimiterHighRates (line 106) | func TestBucketLimiterHighRates(t *testing.T) {
  function BenchmarkBucketLimiter (line 151) | func BenchmarkBucketLimiter(bm *testing.B) {

FILE: proxy_reader.go
  type proxyReader (line 5) | type proxyReader struct

FILE: rateestimator.go
  constant panicZeroRate (line 9) | panicZeroRate         = "rate can't be zero"
  constant panicNegativeAdjustTo (line 10) | panicNegativeAdjustTo = "adjustTo can't be negative or zero"
  function estimate (line 13) | func estimate(rate uint64, adjustTo time.Duration) (time.Duration, uint6...

FILE: rateestimator_test.go
  function TestRateEstimatorPanicWithZeroRate (line 8) | func TestRateEstimatorPanicWithZeroRate(t *testing.T) {
  function TestRateEstimatorPanicWithNegativeAdjustTo (line 23) | func TestRateEstimatorPanicWithNegativeAdjustTo(t *testing.T) {
  function TestRateEstimatorAccuracy (line 38) | func TestRateEstimatorAccuracy(t *testing.T) {

FILE: templates.go
  type format (line 12) | type format interface
  type knownFormat (line 13) | type knownFormat
    method template (line 15) | func (kf knownFormat) template() []byte {
  type filePath (line 19) | type filePath
  type userDefinedTemplate (line 20) | type userDefinedTemplate
  function formatFromString (line 22) | func formatFromString(formatSpec string) format {
  constant plainTextTemplate (line 38) | plainTextTemplate = `
  constant jsonTemplate (line 68) | jsonTemplate = `{"spec":{
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (166K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 765,
    "preview": "This is an example of what a **bug report** can look like. Please, feel free to also provide any other information relev"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 891,
    "preview": "Before submitting a pull request be sure to check the code with [gometalinter](https://github.com/alecthomas/gometalinte"
  },
  {
    "path": ".gitignore",
    "chars": 297,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
  },
  {
    "path": ".semaphore/semaphore.yml",
    "chars": 790,
    "preview": "version: v1.0\nname: codesenberg/bombardier\nagent:\n  machine:\n    type: e1-standard-2\n    os_image: ubuntu2004\nblocks:\n  "
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Максим Федосеев\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "README.md",
    "chars": 2847,
    "preview": "# bombardier [![Build Status](https://codesenberg.semaphoreci.com/badges/bombardier/branches/master.svg?key=249c678c-eb2"
  },
  {
    "path": "args_parser.go",
    "chars": 7130,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/alecthomas/kingpin\"\n\t\"github.com/go"
  },
  {
    "path": "args_parser_test.go",
    "chars": 18087,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\tprogramName = \"bombardier\"\n)\n\nfunc Te"
  },
  {
    "path": "bombardier.go",
    "chars": 9379,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"text/template\"\n\t"
  },
  {
    "path": "bombardier_performance_test.go",
    "chars": 1722,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tserverPort = flag.String(\"port\", \"8080\", \"port to"
  },
  {
    "path": "bombardier_test.go",
    "chars": 17277,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"container/ring\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/"
  },
  {
    "path": "build.py",
    "chars": 1266,
    "preview": "import argparse\nimport os\nimport subprocess\n\nplatforms = [\n    (\"darwin\", \"amd64\"),\n    (\"darwin\", \"arm64\"),\n    (\"freeb"
  },
  {
    "path": "client_cert.go",
    "chars": 1024,
    "preview": "package main\n\nimport (\n\t\"crypto/tls\"\n)\n\n// readClientCert - helper function to read client certificate\n// from pem forma"
  },
  {
    "path": "client_cert_test.go",
    "chars": 687,
    "preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGenerateTLSConfig(t *testing.T) {\n\texpectations := []struct {\n\t\tcertPath s"
  },
  {
    "path": "clients.go",
    "chars": 4394,
    "preview": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/valyala/"
  },
  {
    "path": "clients_test.go",
    "chars": 3676,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/g"
  },
  {
    "path": "cmd/utils/simplebenchserver/doc.go",
    "chars": 296,
    "preview": "/*\nSimple HTTP server used for benchmarking.\n\nFollowing options are available:\n\n\t    --help         Show context-sensiti"
  },
  {
    "path": "cmd/utils/simplebenchserver/main.go",
    "chars": 1038,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/alecthomas/kingpin\"\n\t\"github.com/valyala/fasthttp\"\n)\n\nv"
  },
  {
    "path": "common.go",
    "chars": 1609,
    "preview": "package main\n\nimport (\n\t\"errors\"\n\t\"net/url\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/goware/urlx\"\n)\n\nconst (\n\tdecBase = 10\n\n\trateLi"
  },
  {
    "path": "completion_barriers.go",
    "chars": 2447,
    "preview": "package main\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype completionBarrier interface {\n\tcompleted() float64\n\ttryGra"
  },
  {
    "path": "completion_barriers_test.go",
    "chars": 2326,
    "preview": "package main\n\nimport (\n\t\"math\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCouintingCompletionBarrierWait(t *testing.T) {\n\tparties :="
  },
  {
    "path": "config.go",
    "chars": 3502,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"sort\"\n\t\"time\"\n)\n\ntype config struct {\n\tnumConns                  uint64\n\tnumR"
  },
  {
    "path": "config_test.go",
    "chars": 9556,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tdefaultNumberOfReqs = uint64(10000)\n)\n\nfunc TestCanHaveBody(t *testi"
  },
  {
    "path": "dialer.go",
    "chars": 1419,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype countingConn struct {\n\tnet.Conn\n\tbytesRead, byte"
  },
  {
    "path": "doc.go",
    "chars": 3169,
    "preview": "/*\nCommand line utility bombardier is a fast cross-platform HTTP\nbenchmarking tool written in Go.\n\nInstallation with Go "
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "chars": 518,
    "preview": "### Contribution Guidelines\nFor relevant info on how to format commit messages and check the code before submitting pull"
  },
  {
    "path": "error_map.go",
    "chars": 1492,
    "preview": "package main\n\nimport (\n\t\"sort\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\ntype errorMap struct {\n\tmu sync.RWMutex\n\tm  map[stri"
  },
  {
    "path": "error_map_test.go",
    "chars": 1314,
    "preview": "package main\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestErrorMapAdd(t *testing.T) {\n\tm := newErrorMap()\n\terr "
  },
  {
    "path": "flags.go",
    "chars": 983,
    "preview": "package main\n\nimport (\n\t\"strconv\"\n\t\"time\"\n)\n\nconst (\n\tnilStr = \"nil\"\n)\n\ntype nullableUint64 struct {\n\tval *uint64\n}\n\nfun"
  },
  {
    "path": "flags_test.go",
    "chars": 2114,
    "preview": "package main\n\nimport (\n\t\"math\"\n\t\"math/big\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNullableUint64ConversionToString(t "
  },
  {
    "path": "format.go",
    "chars": 887,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n)\n\ntype units struct {\n\tscale uint64\n\tbase  string\n\tunits []string\n}\n\nvar (\n\tbinaryUnits ="
  },
  {
    "path": "format_test.go",
    "chars": 1184,
    "preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nconst (\n\tKB = 1024\n\tMB = KB * 1024\n\tGB = MB * 1024\n\n\tK = 1000\n\tM = K * 1000\n)\n\nfunc"
  },
  {
    "path": "go.mod",
    "chars": 954,
    "preview": "module github.com/codesenberg/bombardier\n\ngo 1.22\n\ntoolchain go1.24.0\n\nrequire (\n\tgithub.com/alecthomas/kingpin v2.2.6+i"
  },
  {
    "path": "go.sum",
    "chars": 6538,
    "preview": "github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purel"
  },
  {
    "path": "headers.go",
    "chars": 461,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype header struct {\n\tkey, value string\n}\n\ntype headersList []header\n\nfunc ("
  },
  {
    "path": "headers_test.go",
    "chars": 1181,
    "preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestHeadersToStringConversion(t *testing.T) {\n\texpectations := []struct {\n\t\tin"
  },
  {
    "path": "internal/test_info.go",
    "chars": 6873,
    "preview": "package internal\n\nimport (\n\t\"math\"\n\t\"net/url\"\n\t\"sort\"\n\t\"time\"\n)\n\n// TestInfo holds information about what specification "
  },
  {
    "path": "limiter.go",
    "chars": 969,
    "preview": "package main\n\nimport (\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n)\n\ntype token uint64\n\nconst (\n\tbrk token = "
  },
  {
    "path": "limiter_barrier_test.go",
    "chars": 1555,
    "preview": "package main\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc TestNoopLimiterCounterBarrierCombination(t *testing.T) "
  },
  {
    "path": "limiter_test.go",
    "chars": 3577,
    "preview": "package main\n\nimport (\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst maxRps = 10000000\n\nfunc TestNoopLim"
  },
  {
    "path": "proxy_reader.go",
    "chars": 66,
    "preview": "package main\n\nimport \"io\"\n\ntype proxyReader struct {\n\tio.Reader\n}\n"
  },
  {
    "path": "rateestimator.go",
    "chars": 708,
    "preview": "package main\n\nimport (\n\t\"math/big\"\n\t\"time\"\n)\n\nconst (\n\tpanicZeroRate         = \"rate can't be zero\"\n\tpanicNegativeAdjust"
  },
  {
    "path": "rateestimator_test.go",
    "chars": 1795,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRateEstimatorPanicWithZeroRate(t *testing.T) {\n\tdefer func() {\n\t\tp"
  },
  {
    "path": "template/doc.go",
    "chars": 2493,
    "preview": "/*\nPackage template documents the way user-defined output templates are\nment to be used.\n\nUser-defined templates use Go'"
  },
  {
    "path": "templates.go",
    "chars": 4284,
    "preview": "package main\n\nimport \"strings\"\n\nvar (\n\ttemplates = map[string][]byte{\n\t\t\"plain-text\": []byte(plainTextTemplate),\n\t\t\"json"
  },
  {
    "path": "testbody.txt",
    "chars": 11,
    "preview": "abracadabra"
  },
  {
    "path": "testclient.cert",
    "chars": 1838,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFITCCAwmgAwIBAgIJAMx2fpQ+fhOZMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV\nBAMMG0JvbWJhcmRpZXIgQ2xpZW5"
  },
  {
    "path": "testclient.key",
    "chars": 3243,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKQIBAAKCAgEA1TE9F36zfsSmXbxkfNonYggVA7skrb+10iuLyey5snkOHLsz\nEjmL3Gtux02GaqZ8u3GdfeZ"
  },
  {
    "path": "testserver.cert",
    "chars": 1838,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFITCCAwmgAwIBAgIJALu5MYN2H+2PMA0GCSqGSIb3DQEBCwUAMCYxJDAiBgNV\nBAMMG0JvbWJhcmRpZXIgU2VydmV"
  },
  {
    "path": "testserver.key",
    "chars": 3247,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKgIBAAKCAgEA1laXP9o5eo9YFOdqK/Il1f4AePr7UZ/mq5nbBkA3Pt/5uW1L\nIq0ECJ3+JzGrzIkYjrj6an0"
  }
]

About this extraction

This page contains the full source code of the codesenberg/bombardier GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (143.3 KB), approximately 50.1k tokens, and a symbol index with 258 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!