-
Notifications
You must be signed in to change notification settings - Fork 41
/
reporters.go
103 lines (92 loc) · 3.12 KB
/
reporters.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package stress
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"text/tabwriter"
)
// Reporter represents any function which takes a slice of Results and
// generates a report returned as a slice of bytes and an error in case
// of failure
type Reporter func([]Result) ([]byte, error)
// ReportText returns a computed Metrics struct as aligned, formatted text
func ReportText(results []Result) ([]byte, error) {
m := NewMetrics(results)
out := &bytes.Buffer{}
w := tabwriter.NewWriter(out, 0, 8, 2, '\t', tabwriter.StripEscape)
fmt.Fprintf(w, "Requests\t[total]\t%d\n", m.Requests)
fmt.Fprintf(w, "Duration\t[total]\t%s\n", m.Duration)
fmt.Fprintf(w, "QPS\t[mean]\t%f\n", m.QPS)
fmt.Fprintf(w, "Latencies\t[mean, 50, 95, 99, max]\t%s, %s, %s, %s, %s\n",
m.Latencies.Mean, m.Latencies.P50, m.Latencies.P95, m.Latencies.P99, m.Latencies.Max)
fmt.Fprintf(w, "Bytes In\t[total, mean]\t%d, %.2f\n", m.BytesIn.Total, m.BytesIn.Mean)
fmt.Fprintf(w, "Bytes Out\t[total, mean]\t%d, %.2f\n", m.BytesOut.Total, m.BytesOut.Mean)
fmt.Fprintf(w, "Success\t[ratio]\t%.2f%%\n", m.Success*100)
fmt.Fprintf(w, "Status Codes\t[code:count]\t")
for code, count := range m.StatusCodes {
fmt.Fprintf(w, "%s:%d ", code, count)
}
fmt.Fprintln(w, "\nError Set:")
for _, err := range m.Errors {
fmt.Fprintln(w, err)
}
if err := w.Flush(); err != nil {
return []byte{}, err
}
return out.Bytes(), nil
}
// ReportJSON writes a computed Metrics struct to as JSON
func ReportJSON(results []Result) ([]byte, error) {
return json.Marshal(NewMetrics(results))
}
// ReportPlot builds up a self contained HTML page with an interactive plot
// of the latencies of the requests. Built with http://dygraphs.com/
func ReportPlot(results []Result) ([]byte, error) {
series := &bytes.Buffer{}
for i, point := 0, ""; i < len(results); i++ {
point = "[" + strconv.FormatFloat(
results[i].Timestamp.Sub(results[0].Timestamp).Seconds(), 'f', -1, 32) + ","
if results[i].Error == "" {
point += "NaN," + strconv.FormatFloat(results[i].Latency.Seconds()*1000, 'f', -1, 32) + "],"
} else {
point += strconv.FormatFloat(results[i].Latency.Seconds()*1000, 'f', -1, 32) + ",NaN],"
}
series.WriteString(point)
}
// Remove trailing commas
if series.Len() > 0 {
series.Truncate(series.Len() - 1)
}
return []byte(fmt.Sprintf(plotsTemplate, dygraphJSLibSrc(), series)), nil
}
const plotsTemplate = `<!doctype>
<html>
<head>
<title>Stress Plots</title>
</head>
<body>
<div id="latencies" style="font-family: Courier; width: 100%%; height: 600px"></div>
<a href="#" download="stressplot.png" onclick="this.href = document.getElementsByTagName('canvas')[0].toDataURL('image/png').replace(/^data:image\/[^;]/, 'data:application/octet-stream')">Download as PNG</a>
<script>
%s
</script>
<script>
new Dygraph(
document.getElementById("latencies"),
[%s],
{
title: 'Stress Plot',
labels: ['Seconds', 'ERR', 'OK'],
ylabel: 'Latency (ms)',
xlabel: 'Seconds elapsed',
showRoller: true,
colors: ['#FA7878', '#8AE234'],
legend: 'always',
logscale: true,
strokeWidth: 1.3
}
);
</script>
</body>
</html>`