From c6ad47722b5a38ab7bc409015d466a85c34d2067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senart?= Date: Sat, 17 Aug 2013 19:52:29 +0200 Subject: [PATCH] TimingsPlotReporter --- lib/reporter.go | 11 ++++ lib/{reporters.go => text_reporter.go} | 6 -- lib/timings_plot_reporter.go | 90 ++++++++++++++++++++++++++ main.go | 6 +- 4 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 lib/reporter.go rename lib/{reporters.go => text_reporter.go} (93%) create mode 100644 lib/timings_plot_reporter.go diff --git a/lib/reporter.go b/lib/reporter.go new file mode 100644 index 0000000..1a26694 --- /dev/null +++ b/lib/reporter.go @@ -0,0 +1,11 @@ +package vegeta + +import ( + "io" +) + +// Reporter represents any reporter of the results of the test +type Reporter interface { + Report(io.Writer) error + add(res *result) +} diff --git a/lib/reporters.go b/lib/text_reporter.go similarity index 93% rename from lib/reporters.go rename to lib/text_reporter.go index 55f3fe6..7faf129 100644 --- a/lib/reporters.go +++ b/lib/text_reporter.go @@ -7,12 +7,6 @@ import ( "time" ) -// Reporter represents any reporter of the results of the test -type Reporter interface { - Report(io.Writer) error - add(res *result) -} - // TextReporter prints the test results as text // Metrics incude avg time per request, success ratio, // total number of request, avg bytes in and avg bytes out diff --git a/lib/timings_plot_reporter.go b/lib/timings_plot_reporter.go new file mode 100644 index 0000000..6061d24 --- /dev/null +++ b/lib/timings_plot_reporter.go @@ -0,0 +1,90 @@ +package vegeta + +import ( + "code.google.com/p/plotinum/plot" + "code.google.com/p/plotinum/plotter" + "code.google.com/p/plotinum/plotutil" + "code.google.com/p/plotinum/vg" + "code.google.com/p/plotinum/vg/vgsvg" + "container/list" + "io" + "time" +) + +type TimingsPlotReporter struct { + responses *list.List +} + +// NewTimingsPlotReporter initializes a TimingsPlotReporter +func NewTimingsPlotReporter() *TimingsPlotReporter { + return &TimingsPlotReporter{responses: list.New()} +} + +// add inserts response to be used in the report, sorted by timestamp. +func (r *TimingsPlotReporter) add(res *result) { + // Empty list + if r.responses.Len() == 0 { + r.responses.PushFront(res) + return + } + // Happened after all others + if last := r.responses.Back().Value.(*result); last.timestamp.Before(res.timestamp) { + r.responses.PushBack(res) + return + } + // Happened before all others + if first := r.responses.Front().Value.(*result); first.timestamp.After(res.timestamp) { + r.responses.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) + return + } + } +} + +// Report builds up a plot of the response times of the requests +// in SVG format and writes it to out +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) + } + + p, err := plot.New() + if err != nil { + return err + } + pts := make(plotter.XYs, len(timestamps)) + for i := 0; i < len(pts); i++ { + pts[i].X = timestamps[i].Sub(timestamps[0]).Seconds() + pts[i].Y = timings[i].Seconds() + } + + line, err := plotter.NewLine(pts) + if err != nil { + return err + } + line.Color = plotutil.Color(1) + + p.Add(line) + p.X.Padding = vg.Length(3.0) + p.X.Label.Text = "Time elapsed" + p.Y.Padding = vg.Length(3.0) + p.Y.Label.Text = "Latency (seconds)" + + w, h := vg.Millimeters(float64(len(timestamps))), vg.Centimeters(12.0) + canvas := vgsvg.New(w, h) + p.Draw(plot.MakeDrawArea(canvas)) + + _, err = canvas.WriteTo(out) + return err +} diff --git a/main.go b/main.go index 1143566..269a39c 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ func main() { targetsf = flag.String("targets", "targets.txt", "Targets file") ordering = flag.String("ordering", "random", "Attack ordering [sequential, random]") duration = flag.Duration("duration", 10*time.Second, "Duration of the test") - reporter = flag.String("reporter", "text", "Reporter to use [text]") + reporter = flag.String("reporter", "text", "Reporter to use [text, plot:timings]") output = flag.String("output", "stdout", "Reporter output file") ) flag.Parse() @@ -56,8 +56,10 @@ func main() { switch *reporter { case "text": rep = vegeta.NewTextReporter() + case "plot:timings": + rep = vegeta.NewTimingsPlotReporter() default: - log.Println("reporter provided is not supported. using text") + log.Println("Reporter provided is not supported. using text") rep = vegeta.NewTextReporter() }