Skip to content

Commit

Permalink
metrics: Move estimator from Metrics to LatencyMetrics
Browse files Browse the repository at this point in the history
  • Loading branch information
tsenart committed Jul 12, 2018
1 parent b6cb4d6 commit d8d0b52
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 51 deletions.
95 changes: 54 additions & 41 deletions lib/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,33 +37,8 @@ type Metrics struct {
// Errors is a set of unique errors returned by the targets during the attack.
Errors []string `json:"errors"`

errors map[string]struct{}
success uint64
latencies estimator
}

// LatencyMetrics holds computed request latency metrics.
type LatencyMetrics struct {
// Total is the total latency sum of all requests in an attack.
Total time.Duration `json:"total"`
// Mean is the mean request latency.
Mean time.Duration `json:"mean"`
// P50 is the 50th percentile request latency.
P50 time.Duration `json:"50th"`
// P95 is the 95th percentile request latency.
P95 time.Duration `json:"95th"`
// P99 is the 99th percentile request latency.
P99 time.Duration `json:"99th"`
// Max is the maximum observed request latency.
Max time.Duration `json:"max"`
}

// ByteMetrics holds computed byte flow metrics.
type ByteMetrics struct {
// Total is the total number of flowing bytes in an attack.
Total uint64 `json:"total"`
// Mean is the mean number of flowing bytes per hit.
Mean float64 `json:"mean"`
errors map[string]struct{}
success uint64
}

// Add implements the Add method of the Report interface by adding the given
Expand All @@ -73,11 +48,10 @@ func (m *Metrics) Add(r *Result) {

m.Requests++
m.StatusCodes[strconv.Itoa(int(r.Code))]++
m.Latencies.Total += r.Latency
m.BytesOut.Total += r.BytesOut
m.BytesIn.Total += r.BytesIn

m.latencies.Add(float64(r.Latency))
m.Latencies.Add(r.Latency)

if m.Earliest.IsZero() || m.Earliest.After(r.Timestamp) {
m.Earliest = r.Timestamp
Expand All @@ -91,10 +65,6 @@ func (m *Metrics) Add(r *Result) {
m.End = end
}

if r.Latency > m.Latencies.Max {
m.Latencies.Max = r.Latency
}

if r.Code >= 200 && r.Code < 400 {
m.success++
}
Expand All @@ -121,9 +91,9 @@ func (m *Metrics) Close() {
m.BytesOut.Mean = float64(m.BytesOut.Total) / float64(m.Requests)
m.Success = float64(m.success) / float64(m.Requests)
m.Latencies.Mean = time.Duration(float64(m.Latencies.Total) / float64(m.Requests))
m.Latencies.P50 = time.Duration(m.latencies.Get(0.50))
m.Latencies.P95 = time.Duration(m.latencies.Get(0.95))
m.Latencies.P99 = time.Duration(m.latencies.Get(0.99))
m.Latencies.P50 = m.Latencies.Quantile(0.50)
m.Latencies.P95 = m.Latencies.Quantile(0.95)
m.Latencies.P99 = m.Latencies.Quantile(0.99)
}

func (m *Metrics) init() {
Expand All @@ -135,15 +105,58 @@ func (m *Metrics) init() {
m.errors = map[string]struct{}{}
}

if m.latencies == nil {
if m.Errors == nil {
m.Errors = make([]string, 0)
}
}

// LatencyMetrics holds computed request latency metrics.
type LatencyMetrics struct {
// Total is the total latency sum of all requests in an attack.
Total time.Duration `json:"total"`
// Mean is the mean request latency.
Mean time.Duration `json:"mean"`
// P50 is the 50th percentile request latency.
P50 time.Duration `json:"50th"`
// P95 is the 95th percentile request latency.
P95 time.Duration `json:"95th"`
// P99 is the 99th percentile request latency.
P99 time.Duration `json:"99th"`
// Max is the maximum observed request latency.
Max time.Duration `json:"max"`

estimator estimator
}

// Add adds the given latency to the latency metrics.
func (l *LatencyMetrics) Add(latency time.Duration) {
l.init()
if l.Total += latency; latency > l.Max {
l.Max = latency
}
l.estimator.Add(float64(latency))
}

// Quantile returns the nth quantile from the latency summary.
func (l LatencyMetrics) Quantile(nth float64) time.Duration {
l.init()
return time.Duration(l.estimator.Get(nth))
}

func (l *LatencyMetrics) init() {
if l.estimator == nil {
// This compression parameter value is the recommended value
// for normal uses as per http://javadox.com/com.tdunning/t-digest/3.0/com/tdunning/math/stats/TDigest.html
m.latencies = newTdigestEstimator(100)
l.estimator = newTdigestEstimator(100)
}
}

if m.Errors == nil {
m.Errors = make([]string, 0)
}
// ByteMetrics holds computed byte flow metrics.
type ByteMetrics struct {
// Total is the total number of flowing bytes in an attack.
Total uint64 `json:"total"`
// Mean is the mean number of flowing bytes per hit.
Mean float64 `json:"mean"`
}

type estimator interface {
Expand Down
20 changes: 10 additions & 10 deletions lib/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ func TestMetrics_Add(t *testing.T) {

want := Metrics{
Latencies: LatencyMetrics{
Total: duration("50.005s"),
Mean: duration("5.0005ms"),
P50: duration("5.0005ms"),
P95: duration("9.5005ms"),
P99: duration("9.9005ms"),
Max: duration("10ms"),
Total: duration("50.005s"),
Mean: duration("5.0005ms"),
P50: duration("5.0005ms"),
P95: duration("9.5005ms"),
P99: duration("9.9005ms"),
Max: duration("10ms"),
estimator: got.Latencies.estimator,
},
BytesIn: ByteMetrics{Total: 10240000, Mean: 1024},
BytesOut: ByteMetrics{Total: 5120000, Mean: 512},
Expand All @@ -60,9 +61,8 @@ func TestMetrics_Add(t *testing.T) {
StatusCodes: map[string]int{"500": 3333, "200": 3334, "302": 3333},
Errors: []string{"Internal server error"},

errors: got.errors,
success: got.success,
latencies: got.latencies,
errors: got.errors,
success: got.success,
}

if !reflect.DeepEqual(got, want) {
Expand Down Expand Up @@ -124,7 +124,7 @@ func BenchmarkMetrics(b *testing.B) {
{"dgrisky/go-gk", newDgriskyEstimator(0.5)},
{"influxdata/tdigest", newTdigestEstimator(100)},
} {
m := Metrics{latencies: tc.estimator}
m := Metrics{Latencies: LatencyMetrics{estimator: tc.estimator}}
b.Run("Add/"+tc.name, func(b *testing.B) {
for i := 0; i <= b.N; i++ {
m.Add(&Result{
Expand Down

0 comments on commit d8d0b52

Please sign in to comment.