Skip to content

Commit

Permalink
attack: Preparation for alternative target formats
Browse files Browse the repository at this point in the history
This commit introduces the following changes in preparation for
alternative target formats:

- LazyTargeter renamed to LegacyTargeter
- EagerTargeter refactored to take in any Targeter
- `-format` flag added to the attack command
- Extended documentation of the legacy target format
  • Loading branch information
tsenart committed Jun 29, 2018
1 parent 2bcdfd6 commit ed8efd7
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 116 deletions.
153 changes: 57 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ attack command:
Max open idle connections per target host (default 10000)
-duration duration
Duration of the test [0 = forever]
-format string
Targets format [legacy] (default "legacy")
-h2c
Send HTTP/2 requests without TLS encryption
-header value
Request header
-http2
Expand Down Expand Up @@ -113,47 +117,7 @@ Specifies which profiler to enable during execution. Both *cpu* and
#### `-version`
Prints the version and exits.

### `attack`
```console
$ vegeta attack -h
Usage of vegeta attack:
-body string
Requests body file
-cert string
TLS client PEM encoded certificate file
-connections int
Max open idle connections per target host (default 10000)
-duration duration
Duration of the test [0 = forever]
-header value
Request header
-http2
Send HTTP/2 requests when supported by the server (default true)
-insecure
Ignore invalid server TLS certificates
-keepalive
Use persistent connections (default true)
-key string
TLS client PEM encoded private key file
-laddr value
Local IP address (default 0.0.0.0)
-lazy
Read targets lazily
-output string
Output file (default "stdout")
-rate uint
Requests per second (default 50)
-redirects int
Number of redirects to follow. -1 will not follow but marks as success (default 10)
-root-certs value
TLS root certificate files (comma separated list)
-targets string
Targets file (default "stdin")
-timeout duration
Requests timeout (default 30s)
-workers uint
Initial number of workers (default 10)
```
### `attack` command

#### `-body`
Specifies the file whose content will be set as the body of every
Expand All @@ -172,6 +136,56 @@ The internal concurrency structure's setup has this value as a variable.
The actual run time of the test can be longer than specified due to the
responses delay. Use 0 for an infinite attack.

#### `-format`
Specifies the targets format to decode.

##### `legacy` format

The ill-defined legacy format almost resembles the plain-text HTTP message format but
doesn't support in-line HTTP bodies, only references to files that are loaded and used
as request bodies (as exemplified below).

Although targets in this format can be produced by other programs, it was originally
meant to be used by people writing targets by hand for simple use cases.

Here are a few examples of valid targets files in the legacy format:

Simple targets
```
GET http://goku:9090/path/to/dragon?item=ball
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success
```

Targets with custom headers
```
GET http://user:password@goku:9090/path/to
X-Account-ID: 8675309
DELETE http://goku:9090/path/to/remove
Confirmation-Token: 90215
Authorization: Token DEADBEEF
```

Targets with custom bodies
```
POST http://goku:9090/things
@/path/to/newthing.json
PATCH http://goku:9090/thing/71988591
@/path/to/thing-71988591.json
```

Targets with custom bodies and headers
```
POST http://goku:9090/things
X-Account-ID: 99
@/path/to/newthing.json
```

#### `-h2c`
Specifies that HTTP2 requests are to be sent over TCP without TLS encryption.

#### `-header`
Specifies a request header to be used in all targets defined, see `-targets`.
You can specify as many as needed by repeating the flag.
Expand Down Expand Up @@ -220,39 +234,6 @@ list. If unspecified, the default system CAs certificates will be used.
Specifies the attack targets in a line separated file, defaulting to stdin.
The format should be as follows, combining any or all of the following:

Simple targets
```
GET http://goku:9090/path/to/dragon?item=balls
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success
```

Targets with custom headers
```
GET http://user:password@goku:9090/path/to
X-Account-ID: 8675309
DELETE http://goku:9090/path/to/remove
Confirmation-Token: 90215
Authorization: Token DEADBEEF
```

Targets with custom bodies
```
POST http://goku:9090/things
@/path/to/newthing.json
PATCH http://goku:9090/thing/71988591
@/path/to/thing-71988591.json
```

Targets with custom bodies and headers
```
POST http://goku:9090/things
X-Account-ID: 99
@/path/to/newthing.json
```

#### `-timeout`
Specifies the timeout for each request. The default is 0 which disables
timeouts.
Expand All @@ -262,17 +243,7 @@ Specifies the initial number of workers used in the attack. The actual
number of workers will increase if necessary in order to sustain the
requested rate.

### report
```console
$ vegeta report -h
Usage of vegeta report:
-inputs string
Input files (comma separated) (default "stdin")
-output string
Output file (default "stdout")
-reporter string
Reporter [text, json, plot, hist[buckets]] (default "text")
```
### report command

#### `-inputs`
Specifies the input files to generate the report of, defaulting to stdin.
Expand Down Expand Up @@ -363,17 +334,7 @@ Bucket # % Histogram
[6ms, +Inf] 4771 25.93% ###################
```

### `dump`
```console
$ vegeta dump -h
Usage of vegeta dump:
-dumper string
Dumper [json, csv] (default "json")
-inputs string
Input files (comma separated) (default "stdin")
-output string
Output file (default "stdout")
```
### `dump` command

#### `-inputs`
Specifies the input files containing attack results to be dumped. You can specify more than one (comma separated).
Expand Down
16 changes: 12 additions & 4 deletions attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func attackCmd() command {

fs.StringVar(&opts.name, "name", "", "Attack name")
fs.StringVar(&opts.targetsf, "targets", "stdin", "Targets file")
fs.StringVar(&opts.format, "format", "legacy", "Targets format [legacy]")
fs.StringVar(&opts.outputf, "output", "stdout", "Output file")
fs.StringVar(&opts.bodyf, "body", "", "Requests body file")
fs.StringVar(&opts.certf, "cert", "", "TLS client PEM encoded certificate file")
Expand Down Expand Up @@ -59,6 +60,7 @@ var (
type attackOpts struct {
name string
targetsf string
format string
outputf string
bodyf string
certf string
Expand Down Expand Up @@ -111,10 +113,16 @@ func attack(opts *attackOpts) (err error) {
src = files[opts.targetsf]
hdr = opts.headers.Header
)
if opts.lazy {
tr = vegeta.NewLazyTargeter(src, body, hdr)
} else if tr, err = vegeta.NewEagerTargeter(src, body, hdr); err != nil {
return err

switch opts.format {
case "legacy":
tr = vegeta.NewLegacyTargeter(src, body, hdr)
}

if !opts.lazy {
if tr, err = vegeta.NewEagerTargeter(tr); err != nil {
return err
}
}

out, err := file(opts.outputf, true)
Expand Down
27 changes: 17 additions & 10 deletions lib/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,38 +64,45 @@ func NewStaticTargeter(tgts ...Target) Targeter {
}
}

// NewEagerTargeter eagerly reads all Targets out of the provided io.Reader and
// NewEagerTargeter eagerly reads all Targets out of the provided Targeter and
// returns a NewStaticTargeter with them.
//
// body will be set as the Target's body if no body is provided.
// hdr will be merged with the each Target's headers.
func NewEagerTargeter(src io.Reader, body []byte, header http.Header) (Targeter, error) {
func NewEagerTargeter(t Targeter) (Targeter, error) {
var (
sc = NewLazyTargeter(src, body, header)
tgts []Target
tgt Target
err error
)

for {
if err = sc(&tgt); err == ErrNoTargets {
if err = t(&tgt); err == ErrNoTargets {
break
} else if err != nil {
return nil, err
}
tgts = append(tgts, tgt)
}

if len(tgts) == 0 {
return nil, ErrNoTargets
}

return NewStaticTargeter(tgts...), nil
}

// NewLazyTargeter returns a new Targeter that lazily scans Targets from the
// provided io.Reader on every invocation.
// NewLegacyTargeter returns a new Targeter that decodes one Target from the
// given io.Reader on every invocation. The format is as follows:
//
// GET https://foo.bar/a/b/c
// Header-X: 123
// Header-Y: 321
// @/path/to/body/file
//
// POST https://foo.bar/b/c/a
// Header-X: 123
//
// body will be set as the Target's body if no body is provided.
// hdr will be merged with the each Target's headers.
func NewLazyTargeter(src io.Reader, body []byte, hdr http.Header) Targeter {
func NewLegacyTargeter(src io.Reader, body []byte, hdr http.Header) Targeter {
var mu sync.Mutex
sc := peekingScanner{src: bufio.NewScanner(src)}
return func(tgt *Target) (err error) {
Expand Down
12 changes: 6 additions & 6 deletions lib/targets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestNewEagerTargeter(t *testing.T) {
t.Parallel()

src := []byte("GET http://:6060/\nHEAD http://:6606/")
read, err := NewEagerTargeter(bytes.NewReader(src), []byte("body"), nil)
read, err := NewEagerTargeter(NewLegacyTargeter(bytes.NewReader(src), []byte("body"), nil))
if err != nil {
t.Fatalf("Couldn't parse valid source: %s", err)
}
Expand All @@ -89,7 +89,7 @@ func TestNewEagerTargeter(t *testing.T) {
}
}

func TestNewLazyTargeter(t *testing.T) {
func TestNewLegacyTargeter(t *testing.T) {
t.Parallel()

for want, def := range map[error]string{
Expand All @@ -111,7 +111,7 @@ func TestNewLazyTargeter(t *testing.T) {
: 1234`,
} {
src := bytes.NewBufferString(strings.TrimSpace(def))
read := NewLazyTargeter(src, []byte{}, http.Header{})
read := NewLegacyTargeter(src, []byte{}, http.Header{})
if got := read(&Target{}); got == nil || !strings.HasPrefix(got.Error(), want.Error()) {
t.Errorf("got: %s, want: %s\n%s", got, want, def)
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func TestNewLazyTargeter(t *testing.T) {
)

src := bytes.NewBufferString(strings.TrimSpace(targets))
read := NewLazyTargeter(src, []byte{}, http.Header{"Content-Type": []string{"text/plain"}})
read := NewLegacyTargeter(src, []byte{}, http.Header{"Content-Type": []string{"text/plain"}})
for _, want := range []Target{
{
Method: "GET",
Expand Down Expand Up @@ -215,13 +215,13 @@ func TestNewLazyTargeter(t *testing.T) {
func TestErrNilTarget(t *testing.T) {
t.Parallel()

eager, err := NewEagerTargeter(strings.NewReader("GET http://foo.bar"), nil, nil)
eager, err := NewEagerTargeter(NewLegacyTargeter(strings.NewReader("GET http://foo.bar"), nil, nil))
if err != nil {
t.Fatal(err)
}
for i, tr := range []Targeter{
NewStaticTargeter(Target{Method: "GET", URL: "http://foo.bar"}),
NewLazyTargeter(strings.NewReader("GET http://foo.bar"), nil, nil),
NewLegacyTargeter(strings.NewReader("GET http://foo.bar"), nil, nil),
eager,
} {
if got, want := tr(nil), ErrNilTarget; got != want {
Expand Down

0 comments on commit ed8efd7

Please sign in to comment.