/
stats.go
93 lines (84 loc) · 1.75 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
package main
import (
"fmt"
"math"
"sort"
"sync"
"time"
)
type StatSink struct {
input chan time.Duration
stats chan Statistics
seen []time.Duration
waitGroup *sync.WaitGroup
}
type Statistics struct {
count uint32
average time.Duration
percentile90 time.Duration
percentile99 time.Duration
percentile100 time.Duration
}
func (s Statistics) String() string {
return fmt.Sprintf(
"Statistics{count: %d, avg: %s, p90: %s, p99: %s, p100: %s}", s.count, s.average, s.percentile90, s.percentile99, s.percentile100,
)
}
func (ss *StatSink) Put(datum time.Duration) {
ss.input <- datum
}
func (ss *StatSink) MakeStats() Statistics {
ss.stats <- Statistics{}
return <-ss.stats
}
func (ss *StatSink) Stop() error {
close(ss.stats)
ss.waitGroup.Wait()
return nil
}
func StartNewStatSink() *StatSink {
var waitGroup sync.WaitGroup
ss := StatSink{
input: make(chan time.Duration),
stats: make(chan Statistics),
waitGroup: &waitGroup,
}
ss.waitGroup.Add(1)
go func() {
for {
select {
case i := <-ss.input:
ss.seen = append(ss.seen, i)
case _, ok := <-ss.stats:
if !ok {
ss.waitGroup.Done()
return
} else {
ss.stats <- ss.doLogSummary()
}
}
}
}()
return &ss
}
func (ss *StatSink) doLogSummary() Statistics {
count := len(ss.seen)
sort.Slice(ss.seen, func(i, j int) bool { return ss.seen[i] < ss.seen[j] })
var sum time.Duration
for _, datum := range ss.seen {
sum += datum
}
avg := time.Duration(float64(sum) / float64(count))
if count == 0 {
return Statistics{}
}
stats := Statistics{
uint32(count),
avg,
ss.seen[int(math.Round(float64(count)*0.90))-1],
ss.seen[int(math.Round(float64(count)*0.99))-1],
ss.seen[count-1],
}
ss.seen = nil
return stats
}