20
README.md
20
README.md
@@ -30,7 +30,7 @@ Usage of vegeta:
|
|||||||
-ordering="random": Attack ordering [sequential, random]
|
-ordering="random": Attack ordering [sequential, random]
|
||||||
-output="stdout": Reporter output file
|
-output="stdout": Reporter output file
|
||||||
-rate=50: Requests per second
|
-rate=50: Requests per second
|
||||||
-reporter="text": Reporter to use [text, plot:timings]
|
-reporter="text": Reporter to use [text, json, plot:timings]
|
||||||
-targets="targets.txt": Targets file
|
-targets="targets.txt": Targets file
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,6 +75,24 @@ Error Set:
|
|||||||
Server Timeout
|
Server Timeout
|
||||||
Page Not Found
|
Page Not Found
|
||||||
```
|
```
|
||||||
|
##### -reporter=json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total_requests": 50,
|
||||||
|
"total_timing": 34779791,
|
||||||
|
"mean_timing": 695595,
|
||||||
|
"total_bytes_in": 272850,
|
||||||
|
"mean_bytes_in": 5457,
|
||||||
|
"total_bytes_out": 0,
|
||||||
|
"mean_bytes_out": 0,
|
||||||
|
"total_success": 50,
|
||||||
|
"mean_success": 1,
|
||||||
|
"status_codes": {
|
||||||
|
"200": 50
|
||||||
|
},
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
```
|
||||||
##### -reporter=plot:timings
|
##### -reporter=plot:timings
|
||||||
Plots the request timings in SVG format.
|
Plots the request timings in SVG format.
|
||||||

|

|
||||||
|
|||||||
56
lib/metrics.go
Normal file
56
lib/metrics.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package vegeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics holds the stats computed out of a slice of Results
|
||||||
|
// that is used for some of the Reporters
|
||||||
|
type Metrics struct {
|
||||||
|
TotalRequests uint64 `json:"total_requests"`
|
||||||
|
TotalTiming time.Duration `json:"total_timing"`
|
||||||
|
MeanTiming time.Duration `json:"mean_timing"`
|
||||||
|
TotalBytesIn uint64 `json:"total_bytes_in"`
|
||||||
|
MeanBytesIn float64 `json:"mean_bytes_in"`
|
||||||
|
TotalBytesOut uint64 `json:"total_bytes_out"`
|
||||||
|
MeanBytesOut float64 `json:"mean_bytes_out"`
|
||||||
|
TotalSuccess uint64 `json:"total_success"`
|
||||||
|
MeanSuccess float64 `json:"mean_success"`
|
||||||
|
StatusCodes map[string]int `json:"status_codes"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetrics computes and returns a Metrics struct out of a slice of Results
|
||||||
|
func NewMetrics(results []Result) *Metrics {
|
||||||
|
m := &Metrics{
|
||||||
|
TotalRequests: uint64(len(results)),
|
||||||
|
StatusCodes: map[string]int{},
|
||||||
|
}
|
||||||
|
errorSet := map[string]struct{}{}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
m.StatusCodes[strconv.Itoa(int(result.Code))]++
|
||||||
|
m.TotalTiming += result.Timing
|
||||||
|
m.TotalBytesOut += result.BytesOut
|
||||||
|
m.TotalBytesIn += result.BytesIn
|
||||||
|
if result.Code >= 200 && result.Code < 300 {
|
||||||
|
m.TotalSuccess++
|
||||||
|
}
|
||||||
|
if result.Error != nil {
|
||||||
|
errorSet[result.Error.Error()] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.MeanTiming = time.Duration(float64(m.TotalTiming) / float64(m.TotalRequests))
|
||||||
|
m.MeanBytesOut = float64(m.TotalBytesOut) / float64(m.TotalRequests)
|
||||||
|
m.MeanBytesIn = float64(m.TotalBytesIn) / float64(m.TotalRequests)
|
||||||
|
m.MeanSuccess = float64(m.TotalSuccess) / float64(m.TotalRequests)
|
||||||
|
|
||||||
|
m.Errors = make([]string, 0, len(errorSet))
|
||||||
|
for err := range errorSet {
|
||||||
|
m.Errors = append(m.Errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
54
lib/metrics_test.go
Normal file
54
lib/metrics_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package vegeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewMetrics(t *testing.T) {
|
||||||
|
m := NewMetrics([]Result{
|
||||||
|
Result{500, time.Now(), 100 * time.Millisecond, 10, 30, errors.New("Internal server error")},
|
||||||
|
Result{200, time.Now(), 20 * time.Millisecond, 20, 20, nil},
|
||||||
|
Result{200, time.Now(), 30 * time.Millisecond, 30, 10, nil},
|
||||||
|
})
|
||||||
|
|
||||||
|
for field, values := range map[string][]float64{
|
||||||
|
"MeanBytesIn": []float64{m.MeanBytesIn, 20.0},
|
||||||
|
"MeanBytesOut": []float64{m.MeanBytesOut, 20.0},
|
||||||
|
"MeanSuccess": []float64{m.MeanSuccess, 0.6666666666666666},
|
||||||
|
} {
|
||||||
|
if values[0] != values[1] {
|
||||||
|
t.Errorf("%s: want: %f, got: %f", field, values[1], values[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, values := range map[string][]time.Duration{
|
||||||
|
"TotalTiming": []time.Duration{m.TotalTiming, 150 * time.Millisecond},
|
||||||
|
"MeanTiming": []time.Duration{m.MeanTiming, 50 * time.Millisecond},
|
||||||
|
} {
|
||||||
|
if values[0] != values[1] {
|
||||||
|
t.Errorf("%s: want: %s, got: %s", field, values[1], values[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, values := range map[string][]uint64{
|
||||||
|
"TotalSuccess": []uint64{m.TotalSuccess, 2},
|
||||||
|
"TotalBytesOut": []uint64{m.TotalBytesOut, 60},
|
||||||
|
"TotalRequests": []uint64{m.TotalRequests, 3},
|
||||||
|
"TotalBytesIn": []uint64{m.TotalBytesIn, 60},
|
||||||
|
} {
|
||||||
|
if values[0] != values[1] {
|
||||||
|
t.Errorf("%s: want: %d, got: %d", field, values[1], values[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.StatusCodes) != 2 || m.StatusCodes["200"] != 2 || m.StatusCodes["500"] != 1 {
|
||||||
|
t.Errorf("StatusCodes: want: %v, got: %v", map[int]int{200: 2, 500: 1}, m.StatusCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := "Internal server error"
|
||||||
|
if len(m.Errors) != 1 || m.Errors[0] != err {
|
||||||
|
t.Errorf("Errors: want: %v, got: %v", []string{err}, m.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"code.google.com/p/plotinum/plotutil"
|
"code.google.com/p/plotinum/plotutil"
|
||||||
"code.google.com/p/plotinum/vg"
|
"code.google.com/p/plotinum/vg"
|
||||||
"code.google.com/p/plotinum/vg/vgsvg"
|
"code.google.com/p/plotinum/vg/vgsvg"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reporter represents any function which takes a slice of Results and
|
// Reporter represents any function which takes a slice of Results and
|
||||||
@@ -17,57 +17,36 @@ import (
|
|||||||
// in case of failure
|
// in case of failure
|
||||||
type Reporter func([]Result, io.Writer) error
|
type Reporter func([]Result, io.Writer) error
|
||||||
|
|
||||||
// ReportText computes and prints some metrics out of results
|
// ReportText writes a computed Metrics struct to out as aligned, formatted text
|
||||||
// as formatted text. Metrics include avg time per request, success ratio,
|
|
||||||
// total number of request, avg bytes in and avg bytes out.
|
|
||||||
func ReportText(results []Result, out io.Writer) error {
|
func ReportText(results []Result, out io.Writer) error {
|
||||||
totalRequests := float64(len(results))
|
m := NewMetrics(results)
|
||||||
totalTime := time.Duration(0)
|
|
||||||
totalBytesOut := uint64(0)
|
|
||||||
totalBytesIn := uint64(0)
|
|
||||||
totalSuccess := uint64(0)
|
|
||||||
histogram := map[uint16]uint64{}
|
|
||||||
errors := map[string]struct{}{}
|
|
||||||
|
|
||||||
for _, res := range results {
|
|
||||||
histogram[res.Code]++
|
|
||||||
totalTime += res.Timing
|
|
||||||
totalBytesOut += res.BytesOut
|
|
||||||
totalBytesIn += res.BytesIn
|
|
||||||
if res.Code >= 200 && res.Code < 300 {
|
|
||||||
totalSuccess++
|
|
||||||
}
|
|
||||||
if res.Error != nil {
|
|
||||||
errors[res.Error.Error()] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avgTime := time.Duration(float64(totalTime) / totalRequests)
|
|
||||||
avgBytesOut := float64(totalBytesOut) / totalRequests
|
|
||||||
avgBytesIn := float64(totalBytesIn) / totalRequests
|
|
||||||
avgSuccess := float64(totalSuccess) / totalRequests
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(out, 0, 8, 2, '\t', tabwriter.StripEscape)
|
w := tabwriter.NewWriter(out, 0, 8, 2, '\t', tabwriter.StripEscape)
|
||||||
fmt.Fprintf(w, "Time(avg)\tRequests\tSuccess\tBytes(rx/tx)\n")
|
fmt.Fprintf(w, "Time(avg)\tRequests\tSuccess\tBytes(rx/tx)\n")
|
||||||
fmt.Fprintf(w, "%s\t%d\t%.2f%%\t%.2f/%.2f\n", avgTime, int(totalRequests), avgSuccess*100, avgBytesIn, avgBytesOut)
|
fmt.Fprintf(w, "%s\t%d\t%.2f%%\t%.2f/%.2f\n", m.MeanTiming, m.TotalRequests, m.MeanSuccess*100, m.MeanBytesIn, m.MeanBytesOut)
|
||||||
|
|
||||||
fmt.Fprintf(w, "\nCount:\t")
|
fmt.Fprintf(w, "\nCount:\t")
|
||||||
for _, count := range histogram {
|
for _, count := range m.StatusCodes {
|
||||||
fmt.Fprintf(w, "%d\t", count)
|
fmt.Fprintf(w, "%d\t", count)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\nStatus:\t")
|
fmt.Fprintf(w, "\nStatus:\t")
|
||||||
for code := range histogram {
|
for code := range m.StatusCodes {
|
||||||
fmt.Fprintf(w, "%d\t", code)
|
fmt.Fprintf(w, "%d\t", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(w, "\n\nError Set:")
|
fmt.Fprintln(w, "\n\nError Set:")
|
||||||
for err := range errors {
|
for err := range m.Errors {
|
||||||
fmt.Fprintln(w, err)
|
fmt.Fprintln(w, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReportJSON writes a computed Metrics struct to out as JSON
|
||||||
|
func ReportJSON(results []Result, out io.Writer) error {
|
||||||
|
return json.NewEncoder(out).Encode(NewMetrics(results))
|
||||||
|
}
|
||||||
|
|
||||||
// ReportTimingsPlot builds up a plot of the response times of the requests
|
// ReportTimingsPlot builds up a plot of the response times of the requests
|
||||||
// in SVG format and writes it to out
|
// in SVG format and writes it to out
|
||||||
func ReportTimingsPlot(results []Result, out io.Writer) error {
|
func ReportTimingsPlot(results []Result, out io.Writer) error {
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -17,7 +17,7 @@ func main() {
|
|||||||
targetsf = flag.String("targets", "targets.txt", "Targets file")
|
targetsf = flag.String("targets", "targets.txt", "Targets file")
|
||||||
ordering = flag.String("ordering", "random", "Attack ordering [sequential, random]")
|
ordering = flag.String("ordering", "random", "Attack ordering [sequential, random]")
|
||||||
duration = flag.Duration("duration", 10*time.Second, "Duration of the test")
|
duration = flag.Duration("duration", 10*time.Second, "Duration of the test")
|
||||||
reporter = flag.String("reporter", "text", "Reporter to use [text, plot:timings]")
|
reporter = flag.String("reporter", "text", "Reporter to use [text, json, plot:timings]")
|
||||||
output = flag.String("output", "stdout", "Reporter output file")
|
output = flag.String("output", "stdout", "Reporter output file")
|
||||||
cpus = flag.Int("cpus", runtime.NumCPU(), "Number of CPUs to use")
|
cpus = flag.Int("cpus", runtime.NumCPU(), "Number of CPUs to use")
|
||||||
)
|
)
|
||||||
@@ -72,6 +72,8 @@ func run(rate uint64, duration time.Duration, targetsf, ordering, reporter, outp
|
|||||||
switch reporter {
|
switch reporter {
|
||||||
case "text":
|
case "text":
|
||||||
rep = vegeta.ReportText
|
rep = vegeta.ReportText
|
||||||
|
case "json":
|
||||||
|
rep = vegeta.ReportJSON
|
||||||
case "plot:timings":
|
case "plot:timings":
|
||||||
rep = vegeta.ReportTimingsPlot
|
rep = vegeta.ReportTimingsPlot
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user