Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON targets format #300

Merged
merged 11 commits into from
Jul 10, 2018
8 changes: 7 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ COMMIT=$(shell git rev-parse HEAD)
VERSION=$(shell git describe --tags --exact-match --always)
DATE=$(shell date +'%FT%TZ%z')

vegeta: vendor
vegeta: vendor generate
CGO_ENABLED=0 go build -v -a -tags=netgo \
-ldflags '-s -w -extldflags "-static" -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.Date=$(DATE)'

clean-vegeta:
rm vegeta

generate: vendor
go install ./internal/cmd/...
go generate ./...

vendor:
dep ensure -v

Expand Down
166 changes: 70 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 [http, json] (default "http")
-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,69 @@ 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.

##### `json` format

The JSON format makes integration with programs that produce targets dynamically easier.
Each target is one JSON object in its own line. The method and url fields are required.
If present, the body field must be base64 encoded. The generated [JSON Schema](lib/target.schema.json)
defines the format in detail.

```bash
jq -ncM '{method: "GET", url: "http://goku", body: "Punch!" | @base64, header: {"Content-Type": ["text/plain"]}}' |
vegeta attack -format=json -rate=100 | vegeta dump
```

##### `http` format

The http format almost resembles the plain-text HTTP message format defined in
[RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) but it
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 http 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 +247,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 +256,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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason why you have removed the explanation of report and dump command?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it was redundant. It's already included in the output of vegeta -h.


#### `-inputs`
Specifies the input files to generate the report of, defaulting to stdin.
Expand Down Expand Up @@ -363,17 +347,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
25 changes: 21 additions & 4 deletions attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"os"
"os/signal"
"strings"
"time"

vegeta "github.com/tsenart/vegeta/lib"
Expand All @@ -25,6 +26,8 @@ func attackCmd() command {

fs.StringVar(&opts.name, "name", "", "Attack name")
fs.StringVar(&opts.targetsf, "targets", "stdin", "Targets file")
fs.StringVar(&opts.format, "format", vegeta.HTTPTargetFormat,
fmt.Sprintf("Targets format [%s]", strings.Join(vegeta.TargetFormats, ", ")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this breaking odd, it doesn't give consistent guidance how to break long lines and makes it hard to edit. Might be preferrable to use the following rule:

func myFunc(
	param1,
	param2,
	param3,
)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss this today in our pairing session.

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 +62,7 @@ var (
type attackOpts struct {
name string
targetsf string
format string
outputf string
bodyf string
certf string
Expand Down Expand Up @@ -111,10 +115,23 @@ 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 vegeta.JSONTargetFormat:
tr = vegeta.NewJSONTargeter(src, body, hdr)
case vegeta.HTTPTargetFormat:
tr = vegeta.NewHTTPTargeter(src, body, hdr)
default:
return fmt.Errorf("format %q isn't one of [%s]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

opts.format, strings.Join(vegeta.TargetFormats, ", "))
}

if !opts.lazy {
targets, err := vegeta.ReadAllTargets(tr)
if err != nil {
return err
}
tr = vegeta.NewStaticTargeter(targets...)
}

out, err := file(opts.outputf, true)
Expand Down
63 changes: 63 additions & 0 deletions internal/cmd/jsonschema/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/alecthomas/jsonschema"

vegeta "github.com/tsenart/vegeta/lib"
)

func main() {
types := map[string]interface{}{
"Target": &vegeta.Target{},
}

valid := strings.Join(keys(types), ", ")

fs := flag.NewFlagSet("jsonschema", flag.ContinueOnError)
typ := fs.String("type", "", fmt.Sprintf("Vegeta type to generate a JSON schema for [%s]", valid))
out := fs.String("output", "stdout", "Output file")

if err := fs.Parse(os.Args[1:]); err != nil {
die("%s", err)
}

t, ok := types[*typ]
if !ok {
die("invalid type %q not in [%s]", *typ, valid)
}

schema, err := json.MarshalIndent(jsonschema.Reflect(t), "", " ")
if err != nil {
die("%s", err)
}

switch *out {
case "stdout":
_, err = os.Stdout.Write(schema)
default:
err = ioutil.WriteFile(*out, schema, 0644)
}

if err != nil {
die("%s", err)
}
}

func die(s string, args ...interface{}) {
fmt.Fprintf(os.Stderr, s, args...)
os.Exit(1)
}

func keys(types map[string]interface{}) (ks []string) {
for k := range types {
ks = append(ks, k)
}
return ks
}
Loading