Skip to content

Commit

Permalink
Include histogram in JSON output (#395)
Browse files Browse the repository at this point in the history
* report: Add -buckets option for -type=json

Closes #394

* report: Add support for -buckets option for -type=hist too
  • Loading branch information
fxkr authored and tsenart committed Jun 24, 2019
1 parent 0f5577e commit 2870712
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 6 deletions.
10 changes: 10 additions & 0 deletions README.md
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},
"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
@@ -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
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
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

0 comments on commit 2870712

Please sign in to comment.