Skip to content

Commit

Permalink
Vegeta dump command
Browse files Browse the repository at this point in the history
Fixes #95
  • Loading branch information
tsenart committed Nov 21, 2014
1 parent 370ed37 commit d5abca4
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 6 deletions.
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", "", "Reporter [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 d5abca4

Please sign in to comment.