Skip to content

Commit

Permalink
report: Downsample time series in plot reporter
Browse files Browse the repository at this point in the history
This commit introduces timeseries downsampling in the plot reporter
with the aim to be performant even with very large result sets.

Fixes #137
  • Loading branch information
tsenart committed Jul 13, 2018
1 parent 7a6744a commit c8e7de8
Showing 1 changed file with 53 additions and 32 deletions.
85 changes: 53 additions & 32 deletions lib/reporters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"encoding/json"
"fmt"
"io"
"sort"
"strconv"
"strings"
"text/tabwriter"

lttb "github.com/dgryski/go-lttb"
"github.com/lucasb-eyer/go-colorful"
)

Expand Down Expand Up @@ -109,51 +111,70 @@ func NewJSONReporter(m *Metrics) Reporter {
// http://dygraphs.com/
func NewPlotReporter(title string, rs *Results) Reporter {
return func(w io.Writer) (err error) {
_, err = fmt.Fprintf(w, plotsTemplateHead, title, asset(dygraphs), asset(html2canvas))
if err != nil {
return err
series := make(map[string][]Results, len(*rs))
for _, r := range *rs {
idx := 0
if r.Error == "" {
idx++
}

if len(series[r.Attack]) == 0 {
series[r.Attack] = make([]Results, 2)
}

series[r.Attack][idx] = append(series[r.Attack][idx], r)
}

attacks := make(map[string]Results, len(*rs))
for _, r := range *rs {
attacks[r.Attack] = append(attacks[r.Attack], r)
samples := make(map[string][][]lttb.Point, len(series))
for attack, results := range series {
for i := range results {
sort.Sort(results[i])
points := make([]lttb.Point, 0, len(results[i]))
for _, r := range results[i] {
points = append(points, lttb.Point{
X: float64(r.Timestamp.Sub(results[i][0].Timestamp).Seconds()),
Y: float64(r.Latency.Seconds() * 1000),
})
}
samples[attack] = append(samples[attack], lttb.LTTB(points, 1000))
}
}

const series = 2 // OK and Errors
i, offsets := 0, make(map[string]int, len(attacks))
for attack := range attacks {
offsets[attack] = 1 + i*series
const count = 2 // OK and Errors
i, offsets := 0, make(map[string]int, len(series))
for name := range series {
offsets[name] = 1 + i*count
i++
}

const nan = "NaN"

data := make([]string, 1+len(attacks)*series)
for attack, results := range attacks {
for i, r := range results {
for j := range data {
data[j] = nan
}
_, err = fmt.Fprintf(w, plotsTemplateHead, title, asset(dygraphs), asset(html2canvas))
if err != nil {
return err
}

offset := offsets[attack]
if r.Error == "" {
offset++
}
const nan = "NaN"

ts := r.Timestamp.Sub(results[0].Timestamp).Seconds()
data[0] = strconv.FormatFloat(ts, 'f', -1, 32)
data := make([]string, 1+len(series)*count)
for attack, results := range samples {
for idx, points := range results {
for i, p := range points {
for j := range data {
data[j] = nan
}

latency := r.Latency.Seconds() * 1000
data[offset] = strconv.FormatFloat(latency, 'f', -1, 32)
offset := offsets[attack] + idx
data[0] = strconv.FormatFloat(p.X, 'f', -1, 32)
data[offset] = strconv.FormatFloat(p.Y, 'f', -1, 32)

s := "[" + strings.Join(data, ",") + "]"
s := "[" + strings.Join(data, ",") + "]"

if i < len(*rs)-1 {
s += ","
}
if i < len(*rs)-1 {
s += ","
}

if _, err = io.WriteString(w, s); err != nil {
return err
if _, err = io.WriteString(w, s); err != nil {
return err
}
}
}
}
Expand Down

0 comments on commit c8e7de8

Please sign in to comment.