Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include histogram in JSON output #395

Merged
merged 3 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ Options:
--type Which report type to generate (text | json | hist[buckets]).
[default: text]

--buckets Histogram buckets, e.g.: '[0,1ms,10ms]'

--every Write the report to --output at every given interval (e.g 100ms)
The default of 0 means the report will only be written after
all results have been processed. [default: 0]
Expand Down Expand Up @@ -434,6 +436,7 @@ The `Error Set` shows a unique set of errors returned by all issued requests. Th
"99th": 3530000,
"max": 3660505
},
"buckets": {"0":9952,"1000000":40,"2000000":6,"3000000":0,"4000000":0,"5000000":2},
tsenart marked this conversation as resolved.
Show resolved Hide resolved
"bytes_in": {
"total": 606700,
"mean": 6067
Expand All @@ -457,6 +460,13 @@ The `Error Set` shows a unique set of errors returned by all issued requests. Th
}
```

In the `buckets` field, each key is a nanosecond value representing the lower bound of a bucket.
The upper bound is implied by the next higher bucket.
Upper bounds are non-inclusive.
The highest bucket is the overflow bucket; it has no upper bound.
The values are counts of how many requests fell into that particular bucket.
If the `-buckets` parameter is not present, the `buckets` field is omitted.

#### `report -type=hist`

Computes and prints a text based histogram for the given buckets.
Expand Down
20 changes: 20 additions & 0 deletions lib/histogram.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vegeta

import (
"bytes"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -35,6 +36,25 @@ func (h *Histogram) Add(r *Result) {
h.Counts[i]++
}

// MarshalJSON returns a JSON encoding of the buckets and their counts.
func (h *Histogram) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer

// Custom marshalling to guarantee order.
buf.WriteString("{")
for i := range h.Buckets {
if i > 0 {
buf.WriteString(", ")
}
if _, err := fmt.Fprintf(&buf, "\"%d\": %d", h.Buckets[i], h.Counts[i]); err != nil {
return nil, err
}
}
buf.WriteString("}")

return buf.Bytes(), nil
}

// Nth returns the nth bucket represented as a string.
func (bs Buckets) Nth(i int) (left, right string) {
if i >= len(bs)-1 {
Expand Down
6 changes: 6 additions & 0 deletions lib/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
type Metrics struct {
// Latencies holds computed request latency metrics.
Latencies LatencyMetrics `json:"latencies"`
// Histogram, only if requested
Histogram *Histogram `json:"buckets,omitempty"`
// BytesIn holds computed incoming byte metrics.
BytesIn ByteMetrics `json:"bytes_in"`
// BytesOut holds computed outgoing byte metrics.
Expand Down Expand Up @@ -75,6 +77,10 @@ func (m *Metrics) Add(r *Result) {
m.Errors = append(m.Errors, r.Error)
}
}

if m.Histogram != nil {
m.Histogram.Add(r)
}
}

// Close implements the Close method of the Report interface by computing
Expand Down
22 changes: 16 additions & 6 deletions report.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func reportCmd() command {
typ := fs.String("type", "text", "Report type to generate [text, json, hist[buckets]]")
every := fs.Duration("every", 0, "Report interval")
output := fs.String("output", "stdout", "Output file")
buckets := fs.String("buckets", "", "Histogram buckets, e.g.: \"[0,1ms,10ms]\"")

fs.Usage = func() {
fmt.Fprintln(os.Stderr, reportUsage)
Expand All @@ -51,11 +52,11 @@ func reportCmd() command {
if len(files) == 0 {
files = append(files, "stdin")
}
return report(files, *typ, *output, *every)
return report(files, *typ, *output, *every, *buckets)
}}
}

func report(files []string, typ, output string, every time.Duration) error {
func report(files []string, typ, output string, every time.Duration, bucketsStr string) error {
if len(typ) < 4 {
return fmt.Errorf("invalid report type: %s", typ)
}
Expand Down Expand Up @@ -85,13 +86,22 @@ func report(files []string, typ, output string, every time.Duration) error {
rep, report = vegeta.NewTextReporter(&m), &m
case "json":
var m vegeta.Metrics
if bucketsStr != "" {
m.Histogram = &vegeta.Histogram{}
if err := m.Histogram.Buckets.UnmarshalText([]byte(bucketsStr)); err != nil {
return err
}
}
rep, report = vegeta.NewJSONReporter(&m), &m
case "hist":
if len(typ) < 6 {
return fmt.Errorf("bad buckets: '%s'", typ[4:])
}
var hist vegeta.Histogram
if err := hist.Buckets.UnmarshalText([]byte(typ[4:])); err != nil {
if bucketsStr == "" { // Old way
if len(typ) < 6 {
return fmt.Errorf("bad buckets: '%s'", typ[4:])
}
bucketsStr = typ[4:]
}
if err := hist.Buckets.UnmarshalText([]byte(bucketsStr)); err != nil {
return err
}
rep, report = vegeta.NewHistogramReporter(&hist), &hist
Expand Down