Publicize vegeta.Result
This commit is contained in:
@@ -13,31 +13,31 @@ import (
|
||||
func Attack(targets Targets, rate uint64, duration time.Duration, rep Reporter) {
|
||||
hits := make(chan *http.Request, rate*uint64((duration).Seconds()))
|
||||
defer close(hits)
|
||||
responses := make(chan *result, cap(hits))
|
||||
defer close(responses)
|
||||
go drill(rate, hits, responses) // Attack!
|
||||
results := make(chan *Result, cap(hits))
|
||||
defer close(results)
|
||||
go drill(rate, hits, results) // Attack!
|
||||
for i := 0; i < cap(hits); i++ {
|
||||
hits <- targets[i%len(targets)]
|
||||
}
|
||||
// Wait for all requests to finish
|
||||
for i := 0; i < cap(responses); i++ {
|
||||
rep.add(<-responses)
|
||||
for i := 0; i < cap(results); i++ {
|
||||
rep.add(<-results)
|
||||
}
|
||||
}
|
||||
|
||||
// result represents the metrics we want out of an http.Response
|
||||
type result struct {
|
||||
code uint64
|
||||
timestamp time.Time
|
||||
timing time.Duration
|
||||
bytesOut uint64
|
||||
bytesIn uint64
|
||||
err error
|
||||
// Result represents the metrics we want out of an http.Response
|
||||
type Result struct {
|
||||
Code uint64
|
||||
Timestamp time.Time
|
||||
Timing time.Duration
|
||||
BytesOut uint64
|
||||
BytesIn uint64
|
||||
Error error
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func drill(rate uint64, reqs chan *http.Request, res chan *Result) {
|
||||
throttle := time.Tick(time.Duration(1e9 / rate))
|
||||
for req := range reqs {
|
||||
<-throttle
|
||||
@@ -48,19 +48,19 @@ func drill(rate uint64, reqs chan *http.Request, res chan *result) {
|
||||
// hit executes the passed http.Request and puts a generated *result into res.
|
||||
// Both transport errors and unsucessfull requests (non {2xx,3xx}) are
|
||||
// considered errors which are set in the Response.
|
||||
func hit(req *http.Request, res chan *result) {
|
||||
func hit(req *http.Request, res chan *Result) {
|
||||
began := time.Now()
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
result := &result{
|
||||
timestamp: began,
|
||||
timing: time.Since(began),
|
||||
bytesOut: uint64(req.ContentLength),
|
||||
err: err,
|
||||
result := &Result{
|
||||
Timestamp: began,
|
||||
Timing: time.Since(began),
|
||||
BytesOut: uint64(req.ContentLength),
|
||||
Error: err,
|
||||
}
|
||||
if err == nil {
|
||||
result.bytesIn, result.code = uint64(r.ContentLength), uint64(r.StatusCode)
|
||||
if body, err := ioutil.ReadAll(r.Body); err != nil && (result.code < 200 || result.code >= 300) {
|
||||
result.err = errors.New(string(body))
|
||||
result.BytesIn, result.Code = uint64(r.ContentLength), uint64(r.StatusCode)
|
||||
if body, err := ioutil.ReadAll(r.Body); err != nil && (result.Code < 200 || result.Code >= 300) {
|
||||
result.Error = errors.New(string(body))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
// Reporter represents any reporter of the results of the test
|
||||
type Reporter interface {
|
||||
Report(io.Writer) error
|
||||
add(res *result)
|
||||
add(res *Result)
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ import (
|
||||
// Metrics incude avg time per request, success ratio,
|
||||
// total number of request, avg bytes in and avg bytes out
|
||||
type TextReporter struct {
|
||||
responses []*result
|
||||
results []*Result
|
||||
}
|
||||
|
||||
// NewTextReporter initializes a TextReporter with n responses
|
||||
func NewTextReporter() *TextReporter {
|
||||
return &TextReporter{responses: make([]*result, 0)}
|
||||
return &TextReporter{results: make([]*Result, 0)}
|
||||
}
|
||||
|
||||
// Report computes and writes the report to out.
|
||||
// It returns an error in case of failure.
|
||||
func (r *TextReporter) Report(out io.Writer) error {
|
||||
totalRequests := len(r.responses)
|
||||
totalRequests := len(r.results)
|
||||
totalTime := time.Duration(0)
|
||||
totalBytesOut := uint64(0)
|
||||
totalBytesIn := uint64(0)
|
||||
@@ -30,16 +30,16 @@ func (r *TextReporter) Report(out io.Writer) error {
|
||||
histogram := map[uint64]uint64{}
|
||||
errors := map[string]struct{}{}
|
||||
|
||||
for _, res := range r.responses {
|
||||
histogram[res.code]++
|
||||
totalTime += res.timing
|
||||
totalBytesOut += res.bytesOut
|
||||
totalBytesIn += res.bytesIn
|
||||
if res.code >= 200 && res.code < 300 {
|
||||
for _, res := range r.results {
|
||||
histogram[res.Code]++
|
||||
totalTime += res.Timing
|
||||
totalBytesOut += res.BytesOut
|
||||
totalBytesIn += res.BytesIn
|
||||
if res.Code >= 200 && res.Code < 300 {
|
||||
totalSuccess++
|
||||
}
|
||||
if res.err != nil {
|
||||
errors[res.err.Error()] = struct{}{}
|
||||
if res.Error != nil {
|
||||
errors[res.Error.Error()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,6 @@ func (r *TextReporter) Report(out io.Writer) error {
|
||||
|
||||
// add adds a response to be used in the report
|
||||
// Order of arrival is not relevant for this reporter
|
||||
func (r *TextReporter) add(res *result) {
|
||||
r.responses = append(r.responses, res)
|
||||
func (r *TextReporter) add(res *Result) {
|
||||
r.results = append(r.results, res)
|
||||
}
|
||||
|
||||
@@ -12,36 +12,36 @@ import (
|
||||
)
|
||||
|
||||
type TimingsPlotReporter struct {
|
||||
responses *list.List
|
||||
results *list.List
|
||||
}
|
||||
|
||||
// NewTimingsPlotReporter initializes a TimingsPlotReporter
|
||||
func NewTimingsPlotReporter() *TimingsPlotReporter {
|
||||
return &TimingsPlotReporter{responses: list.New()}
|
||||
return &TimingsPlotReporter{results: list.New()}
|
||||
}
|
||||
|
||||
// add inserts response to be used in the report, sorted by timestamp.
|
||||
func (r *TimingsPlotReporter) add(res *result) {
|
||||
func (r *TimingsPlotReporter) add(res *Result) {
|
||||
// Empty list
|
||||
if r.responses.Len() == 0 {
|
||||
r.responses.PushFront(res)
|
||||
if r.results.Len() == 0 {
|
||||
r.results.PushFront(res)
|
||||
return
|
||||
}
|
||||
// Happened after all others
|
||||
if last := r.responses.Back().Value.(*result); last.timestamp.Before(res.timestamp) {
|
||||
r.responses.PushBack(res)
|
||||
if last := r.results.Back().Value.(*Result); last.Timestamp.Before(res.Timestamp) {
|
||||
r.results.PushBack(res)
|
||||
return
|
||||
}
|
||||
// Happened before all others
|
||||
if first := r.responses.Front().Value.(*result); first.timestamp.After(res.timestamp) {
|
||||
r.responses.PushFront(res)
|
||||
if first := r.results.Front().Value.(*Result); first.Timestamp.After(res.Timestamp) {
|
||||
r.results.PushFront(res)
|
||||
return
|
||||
}
|
||||
// O(n) worst case insertion time
|
||||
for e := r.responses.Front(); e != nil; e = e.Next() {
|
||||
needle := e.Value.(*result)
|
||||
if res.timestamp.Before(needle.timestamp) {
|
||||
r.responses.InsertBefore(res, e)
|
||||
for e := r.results.Front(); e != nil; e = e.Next() {
|
||||
needle := e.Value.(*Result)
|
||||
if res.Timestamp.Before(needle.Timestamp) {
|
||||
r.results.InsertBefore(res, e)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -53,10 +53,10 @@ func (r *TimingsPlotReporter) Report(out io.Writer) error {
|
||||
timestamps := make([]time.Time, 0)
|
||||
timings := make([]time.Duration, 0)
|
||||
|
||||
for e := r.responses.Front(); e != nil; e = e.Next() {
|
||||
r := e.Value.(*result)
|
||||
timestamps = append(timestamps, r.timestamp)
|
||||
timings = append(timings, r.timing)
|
||||
for e := r.results.Front(); e != nil; e = e.Next() {
|
||||
r := e.Value.(*Result)
|
||||
timestamps = append(timestamps, r.Timestamp)
|
||||
timings = append(timings, r.Timing)
|
||||
}
|
||||
|
||||
p, err := plot.New()
|
||||
|
||||
Reference in New Issue
Block a user