forked from docker/machine
/
stats.go
97 lines (85 loc) · 2.21 KB
/
stats.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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()
}