97 lines
2.2 KiB
Go
97 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"text/tabwriter"
|
|
"time"
|
|
)
|
|
|
|
type byDuration []time.Duration
|
|
|
|
func (data byDuration) Len() int { return len(data) }
|
|
func (data byDuration) Swap(i, j int) { data[i], data[j] = data[j], data[i] }
|
|
func (data byDuration) Less(i, j int) bool { return data[i] < data[j] }
|
|
|
|
// quantile returns a value representing the kth of q quantiles.
|
|
// May alter the order of data.
|
|
func quantile(data []time.Duration, k, q int) (quantile time.Duration, ok bool) {
|
|
if len(data) < 1 {
|
|
return 0, false
|
|
}
|
|
if k > q {
|
|
return 0, false
|
|
}
|
|
if k < 0 || q < 1 {
|
|
return 0, false
|
|
}
|
|
|
|
sort.Sort(byDuration(data))
|
|
|
|
if k == 0 {
|
|
return data[0], true
|
|
}
|
|
if k == q {
|
|
return data[len(data)-1], true
|
|
}
|
|
|
|
bucketSize := float64(len(data)-1) / float64(q)
|
|
i := float64(k) * bucketSize
|
|
|
|
lower := int(math.Trunc(i))
|
|
var upper int
|
|
if i > float64(lower) && lower+1 < len(data) {
|
|
// If the quantile lies between two elements
|
|
upper = lower + 1
|
|
} else {
|
|
upper = lower
|
|
}
|
|
weightUpper := i - float64(lower)
|
|
weightLower := 1 - weightUpper
|
|
return time.Duration(weightLower*float64(data[lower]) + weightUpper*float64(data[upper])), true
|
|
}
|
|
|
|
type aggregate struct {
|
|
min, median, max time.Duration
|
|
p95, p99 time.Duration // percentiles
|
|
}
|
|
|
|
// newAggregate constructs an aggregate from latencies. Returns nil if latencies does not contain aggregateable data.
|
|
func newAggregate(latencies []time.Duration) *aggregate {
|
|
var agg aggregate
|
|
|
|
if len(latencies) == 0 {
|
|
return nil
|
|
}
|
|
var ok bool
|
|
if agg.min, ok = quantile(latencies, 0, 2); !ok {
|
|
return nil
|
|
}
|
|
if agg.median, ok = quantile(latencies, 1, 2); !ok {
|
|
return nil
|
|
}
|
|
if agg.max, ok = quantile(latencies, 2, 2); !ok {
|
|
return nil
|
|
}
|
|
if agg.p95, ok = quantile(latencies, 95, 100); !ok {
|
|
return nil
|
|
}
|
|
if agg.p99, ok = quantile(latencies, 99, 100); !ok {
|
|
return nil
|
|
}
|
|
return &agg
|
|
}
|
|
|
|
func (agg *aggregate) String() string {
|
|
if agg == nil {
|
|
return "no data"
|
|
}
|
|
var buf bytes.Buffer
|
|
tw := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0) // one-space padding
|
|
fmt.Fprintf(tw, "min:\t%v\nmedian:\t%v\nmax:\t%v\n95th percentile:\t%v\n99th percentile:\t%v\n",
|
|
agg.min, agg.median, agg.max, agg.p95, agg.p99)
|
|
tw.Flush()
|
|
return buf.String()
|
|
}
|