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