Extract Metrics out of ReportText

Metrics will be reused with other Reporters
This commit is contained in:
Tomás Senart
2013-09-10 13:32:44 +01:00
parent 4376791da2
commit 6e9f34846b
3 changed files with 115 additions and 33 deletions

55
lib/metrics.go Normal file
View File

@@ -0,0 +1,55 @@
package vegeta
import (
"time"
)
// Metrics holds the stats computed out of a slice of Results
// that is used for some of the Reporters
type Metrics struct {
TotalRequests uint64 `json:"total_requests"`
TotalTiming time.Duration `json:"total_timing"`
MeanTiming time.Duration `json:"mean_timing"`
TotalBytesIn uint64 `json:"total_bytes_in"`
MeanBytesIn float64 `json:"mean_bytes_in"`
TotalBytesOut uint64 `json:"total_bytes_out"`
MeanBytesOut float64 `json:"mean_bytes_out"`
TotalSuccess uint64 `json:"total_success"`
MeanSuccess float64 `json:"mean_success"`
StatusCodes map[uint16]uint64 `json:"status_codes"`
Errors []string `json:"errors"`
}
// NewMetrics computes and returns a Metrics struct out of a slice of Results
func NewMetrics(results []Result) *Metrics {
m := &Metrics{
TotalRequests: uint64(len(results)),
StatusCodes: map[uint16]uint64{},
}
errorSet := map[string]struct{}{}
for _, result := range results {
m.StatusCodes[result.Code]++
m.TotalTiming += result.Timing
m.TotalBytesOut += result.BytesOut
m.TotalBytesIn += result.BytesIn
if result.Code >= 200 && result.Code < 300 {
m.TotalSuccess++
}
if result.Error != nil {
errorSet[result.Error.Error()] = struct{}{}
}
}
m.MeanTiming = time.Duration(float64(m.TotalTiming) / float64(m.TotalRequests))
m.MeanBytesOut = float64(m.TotalBytesOut) / float64(m.TotalRequests)
m.MeanBytesIn = float64(m.TotalBytesIn) / float64(m.TotalRequests)
m.MeanSuccess = float64(m.TotalSuccess) / float64(m.TotalRequests)
m.Errors = make([]string, 0, len(errorSet))
for err, _ := range errorSet {
m.Errors = append(m.Errors, err)
}
return m
}

54
lib/metrics_test.go Normal file
View File

@@ -0,0 +1,54 @@
package vegeta
import (
"errors"
"testing"
"time"
)
func TestNewMetrics(t *testing.T) {
m := NewMetrics([]Result{
Result{500, time.Now(), 100 * time.Millisecond, 10, 30, errors.New("Internal server error")},
Result{200, time.Now(), 20 * time.Millisecond, 20, 20, nil},
Result{200, time.Now(), 30 * time.Millisecond, 30, 10, nil},
})
for field, values := range map[string][]float64{
"MeanBytesIn": []float64{m.MeanBytesIn, 20.0},
"MeanBytesOut": []float64{m.MeanBytesOut, 20.0},
"MeanSuccess": []float64{m.MeanSuccess, 0.6666666666666666},
} {
if values[0] != values[1] {
t.Errorf("%s: want: %f, got: %f", field, values[1], values[0])
}
}
for field, values := range map[string][]time.Duration{
"TotalTiming": []time.Duration{m.TotalTiming, 150 * time.Millisecond},
"MeanTiming": []time.Duration{m.MeanTiming, 50 * time.Millisecond},
} {
if values[0] != values[1] {
t.Errorf("%s: want: %s, got: %s", field, values[1], values[0])
}
}
for field, values := range map[string][]uint64{
"TotalSuccess": []uint64{m.TotalSuccess, 2},
"TotalBytesOut": []uint64{m.TotalBytesOut, 60},
"TotalRequests": []uint64{m.TotalRequests, 3},
"TotalBytesIn": []uint64{m.TotalBytesIn, 60},
} {
if values[0] != values[1] {
t.Errorf("%s: want: %d, got: %d", field, values[1], values[0])
}
}
if len(m.StatusCodes) != 2 || m.StatusCodes[200] != 2 || m.StatusCodes[500] != 1 {
t.Errorf("StatusCodes: want: %v, got: %v", map[int]int{200: 2, 500: 1}, m.StatusCodes)
}
err := "Internal server error"
if len(m.Errors) != 1 || m.Errors[0] != err {
t.Errorf("Errors: want: %v, got: %v", []string{err}, m.Errors)
}
}

View File

@@ -9,7 +9,6 @@ import (
"fmt"
"io"
"text/tabwriter"
"time"
)
// Reporter represents any function which takes a slice of Results and
@@ -17,51 +16,25 @@ import (
// in case of failure
type Reporter func([]Result, io.Writer) error
// ReportText computes and prints some metrics out of results
// as formatted text. Metrics include avg time per request, success ratio,
// total number of request, avg bytes in and avg bytes out.
// ReportText writes a computed Metrics struct to out as aligned, formatted text
func ReportText(results []Result, out io.Writer) error {
totalRequests := float64(len(results))
totalTime := time.Duration(0)
totalBytesOut := uint64(0)
totalBytesIn := uint64(0)
totalSuccess := uint64(0)
histogram := map[uint16]uint64{}
errors := map[string]struct{}{}
for _, res := range results {
histogram[res.Code]++
totalTime += res.Timing
totalBytesOut += res.BytesOut
totalBytesIn += res.BytesIn
if res.Code >= 200 && res.Code < 300 {
totalSuccess++
}
if res.Error != nil {
errors[res.Error.Error()] = struct{}{}
}
}
avgTime := time.Duration(float64(totalTime) / totalRequests)
avgBytesOut := float64(totalBytesOut) / totalRequests
avgBytesIn := float64(totalBytesIn) / totalRequests
avgSuccess := float64(totalSuccess) / totalRequests
m := NewMetrics(results)
w := tabwriter.NewWriter(out, 0, 8, 2, '\t', tabwriter.StripEscape)
fmt.Fprintf(w, "Time(avg)\tRequests\tSuccess\tBytes(rx/tx)\n")
fmt.Fprintf(w, "%s\t%d\t%.2f%%\t%.2f/%.2f\n", avgTime, int(totalRequests), avgSuccess*100, avgBytesIn, avgBytesOut)
fmt.Fprintf(w, "%s\t%d\t%.2f%%\t%.2f/%.2f\n", m.MeanTiming, m.TotalRequests, m.MeanSuccess*100, m.MeanBytesIn, m.MeanBytesOut)
fmt.Fprintf(w, "\nCount:\t")
for _, count := range histogram {
for _, count := range m.StatusCodes {
fmt.Fprintf(w, "%d\t", count)
}
fmt.Fprintf(w, "\nStatus:\t")
for code := range histogram {
for code := range m.StatusCodes {
fmt.Fprintf(w, "%d\t", code)
}
fmt.Fprintln(w, "\n\nError Set:")
for err := range errors {
for err := range m.Errors {
fmt.Fprintln(w, err)
}