Skip to content

Commit

Permalink
Merge pull request #100 from tsenart/dump-cmd
Browse files Browse the repository at this point in the history
Dump cmd
  • Loading branch information
tsenart committed Nov 21, 2014
2 parents 370ed37 + e27d8d7 commit 82dbc45
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 8 deletions.
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ report command:
-output="stdout": Output file
-reporter="text": Reporter [text, json, plot, hist[buckets]]

dump command:
-dumper="": Dumper [json, csv]
-inputs="stdin": Input files (comma separated)
-output="stdout": Output file

global flags:
-cpus=8 Number of CPUs to use

Expand Down Expand Up @@ -171,12 +176,12 @@ means every single hit runs in its own worker.
```
$ vegeta report -h
Usage of vegeta report:
-input="stdin": Input files (comma separated)
-inputs="stdin": Input files (comma separated)
-output="stdout": Output file
-reporter="text": Reporter [text, json, plot, hist[buckets]]
```
#### -input
#### -inputs
Specifies the input files to generate the report of, defaulting to stdin.
These are the output of vegeta attack. You can specify more than one (comma
separated) and they will be merged and sorted before being used by the
Expand Down Expand Up @@ -259,6 +264,32 @@ Bucket # % Histogram
[6ms, +Inf] 4771 25.93% ###################
```

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

#### -inputs
Specifies the input files containing attack results to be dumped. You can specify more than one (comma separated).

#### -output
Specifies the output file to which the dump will be written to.

#### -dumper
Specifies the dump format.

##### json
Dumps attack results as JSON objects.

##### csv
Dumps attack results as CSV records with six columns.
The columns are: unix timestamp in ns since epoch, http status code,
request latency in ns, bytes out, bytes in, and lastly the error.

## Usage (Library)
```go
package main
Expand Down
78 changes: 78 additions & 0 deletions dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"flag"
"fmt"
"io"
"os"
"os/signal"
"strings"

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

func dumpCmd() command {
fs := flag.NewFlagSet("vegeta dump", flag.ExitOnError)
dumper := fs.String("dumper", "", "Dumper [json, csv]")
inputs := fs.String("inputs", "stdin", "Input files (comma separated)")
output := fs.String("output", "stdout", "Output file")
return command{fs, func(args []string) error {
fs.Parse(args)
return dump(*dumper, *inputs, *output)
}}
}

func dump(dumper, inputs, output string) error {
dump, ok := dumpers[dumper]
if !ok {
return fmt.Errorf("unsupported dumper: %s", dumper)
}

files := strings.Split(inputs, ",")
srcs := make([]io.Reader, len(files))
for i, f := range files {
in, err := file(f, false)
if err != nil {
return err
}
defer in.Close()
srcs[i] = in
}

out, err := file(output, true)
if err != nil {
return err
}
defer out.Close()

sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
res, errs := vegeta.Collect(srcs...)

for {
select {
case _ = <-sig:
return nil
case r, ok := <-res:
if !ok {
return nil
}
dmp, err := dump.Dump(r)
if err != nil {
return err
} else if _, err = out.Write(dmp); err != nil {
return err
}
case err, ok := <-errs:
if !ok {
return nil
}
return err
}
}
}

var dumpers = map[string]vegeta.Dumper{
"csv": vegeta.DumpCSV,
"json": vegeta.DumpJSON,
}
42 changes: 42 additions & 0 deletions lib/dumpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package vegeta

import (
"bytes"
"encoding/json"
"fmt"
)

// Dumper is an interface defining Results dumping.
type Dumper interface {
Dump(*Result) ([]byte, error)
}

// DumperFunc is an adapter to allow the use of ordinary functions as
// Dumpers. If f is a function with the appropriate signature, DumperFunc(f)
// is a Dumper object that calls f.
type DumperFunc func(*Result) ([]byte, error)

func (f DumperFunc) Dump(r *Result) ([]byte, error) { return f(r) }

// DumpCSV dumps a Result as a CSV record with six columns.
// The columns are: unix timestamp in ns since epoch, http status code,
// request latency in ns, bytes out, bytes in, and lastly the error.
var DumpCSV DumperFunc = func(r *Result) ([]byte, error) {
var buf bytes.Buffer
_, err := fmt.Fprintf(&buf, "%d,%d,%d,%d,%d,'%s'\n",
r.Timestamp.UnixNano(),
r.Code,
r.Latency.Nanoseconds(),
r.BytesOut,
r.BytesIn,
r.Error,
)
return buf.Bytes(), err
}

// DumpJSON dumps a Result as a JSON object.
var DumpJSON DumperFunc = func(r *Result) ([]byte, error) {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(r)
return buf.Bytes(), err
}
12 changes: 6 additions & 6 deletions lib/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ func init() {
// Result represents the metrics defined out of an http.Response
// generated by each target hit
type Result struct {
Code uint16
Timestamp time.Time
Latency time.Duration
BytesOut uint64
BytesIn uint64
Error string
Code uint16 `json:"code"`
Timestamp time.Time `json:"timestamp"`
Latency time.Duration `json:"latency"`
BytesOut uint64 `json:"bytes_out"`
BytesIn uint64 `json:"bytes_in"`
Error string `json:"error"`
}

// Collect concurrently reads Results from multiple io.Readers until all of
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func main() {
commands := map[string]command{
"attack": attackCmd(),
"report": reportCmd(),
"dump": dumpCmd(),
}

flag.Usage = func() {
Expand Down

0 comments on commit 82dbc45

Please sign in to comment.