diff --git a/lib/attack.go b/lib/attack.go index c66fc33..ef671ee 100644 --- a/lib/attack.go +++ b/lib/attack.go @@ -4,29 +4,17 @@ import ( "errors" "io/ioutil" "net/http" - "sort" "time" ) -// Result represents the metrics defined out of an http.Response -// generated by each target hit -type Result struct { - Code uint16 - Timestamp time.Time - Timing time.Duration - BytesOut uint64 - BytesIn uint64 - Error error -} - // Attack hits the passed Targets (http.Requests) at the rate specified for // duration time and then waits for all the requests to come back. // The results of the attack are put into a slice which is returned. -func Attack(targets Targets, rate uint64, duration time.Duration) []Result { +func Attack(targets Targets, rate uint64, duration time.Duration) Results { total := rate * uint64(duration.Seconds()) hits := make(chan *http.Request, total) res := make(chan Result, total) - results := make(results, total) + results := make(Results, total) // Scatter go drill(rate, hits, res) for i := 0; i < cap(hits); i++ { @@ -39,18 +27,9 @@ func Attack(targets Targets, rate uint64, duration time.Duration) []Result { } close(res) - sort.Sort(results) - - return results + return results.Sort() } -// results is a slice of Result defined only to be sortable with sort.Interface -type results []Result - -func (r results) Len() int { return len(r) } -func (r results) Less(i, j int) bool { return r[i].Timestamp.Before(r[j].Timestamp) } -func (r results) Swap(i, j int) { r[i], r[j] = r[j], r[i] } - // drill loops over the passed reqs channel and executes each request. // It is throttled to the rate specified. func drill(rate uint64, reqs chan *http.Request, res chan Result) { diff --git a/lib/results.go b/lib/results.go new file mode 100644 index 0000000..75c84dd --- /dev/null +++ b/lib/results.go @@ -0,0 +1,46 @@ +package vegeta + +import ( + "encoding/gob" + "io" + "sort" + "time" +) + +// Result represents the metrics defined out of an http.Response +// generated by each target hit +type Result struct { + Code uint16 + Timestamp time.Time + Timing time.Duration + BytesOut uint64 + BytesIn uint64 + Error error +} + +// Results is a slice of Result structs with encoding, +// decoding and sorting behavior attached +type Results []Result + +// WriteTo encodes the results and writes it to an io.Writer +// returning an error in case of failure +func (r Results) WriteTo(out io.Writer) error { + return gob.NewEncoder(out).Encode(r) +} + +// ReadFrom reads data from an io.Reader and decodes it into a Results struct +// returning an error in case of failure +func (r *Results) ReadFrom(in io.Reader) error { + return gob.NewDecoder(in).Decode(r) +} + +// Sort sorts Results by Timestamp in ascending order and returns +// the sorted slice +func (r Results) Sort() Results { + sort.Sort(r) + return r +} + +func (r Results) Len() int { return len(r) } +func (r Results) Less(i, j int) bool { return r[i].Timestamp.Before(r[j].Timestamp) } +func (r Results) Swap(i, j int) { r[i], r[j] = r[j], r[i] } diff --git a/lib/results_test.go b/lib/results_test.go new file mode 100644 index 0000000..b0053fe --- /dev/null +++ b/lib/results_test.go @@ -0,0 +1,50 @@ +package vegeta + +import ( + "bytes" + "sort" + "testing" + "time" +) + +func TestEncoding(t *testing.T) { + results := Results{ + Result{200, time.Now(), 100 * time.Millisecond, 10, 30, nil}, + Result{200, time.Now(), 20 * time.Millisecond, 20, 20, nil}, + Result{200, time.Now(), 30 * time.Millisecond, 30, 10, nil}, + } + buffer := &bytes.Buffer{} + + if err := results.WriteTo(buffer); err != nil { + t.Fatalf("Failed WriteTo: %s", err) + } + + decoded := Results{} + if err := decoded.ReadFrom(buffer); err != nil { + t.Fatalf("Failed ReadFrom: %s", err) + } + + if len(decoded) != len(results) { + t.Fatalf("Length mismatch. Want: %d, Got: %d", len(results), len(decoded)) + } + + for i, result := range results { + if decoded[i].Timestamp != result.Timestamp { + t.Fatalf("Expected result with timestamp: %s, got: %s", result.Timestamp, decoded[i].Timestamp) + } + } +} + +func TestSort(t *testing.T) { + results := Results{ + Result{Timestamp: time.Date(2013, 9, 10, 20, 4, 0, 3, time.UTC)}, + Result{Timestamp: time.Date(2013, 9, 10, 20, 4, 0, 2, time.UTC)}, + Result{Timestamp: time.Date(2013, 9, 10, 20, 4, 0, 1, time.UTC)}, + } + + results.Sort() + + if !sort.IsSorted(results) { + t.Fatalf("Sort failed: %v", results) + } +}