-
Notifications
You must be signed in to change notification settings - Fork 9
/
timer_metrics.go
137 lines (120 loc) · 2.78 KB
/
timer_metrics.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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package timer_metrics
import (
"fmt"
"log"
"math"
"sort"
"sync"
"time"
)
type TimerMetrics struct {
sync.Mutex
timings durations
prefix string
statusEvery int
position int
}
// start a new TimerMetrics to print out metrics every n times
func NewTimerMetrics(statusEvery int, prefix string) *TimerMetrics {
s := &TimerMetrics{
statusEvery: statusEvery,
prefix: prefix,
}
if statusEvery > 0 {
if statusEvery < 100 {
log.Printf("Warning: use more than 100 status values for accurate percentiles (configured with %d)", statusEvery)
}
s.timings = make(durations, 0, statusEvery)
}
return s
}
type durations []time.Duration
func (s durations) Len() int { return len(s) }
func (s durations) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s durations) Less(i, j int) bool { return s[i] < s[j] }
func percentile(perc float64, arr durations) time.Duration {
length := len(arr)
if length == 0 {
return 0
}
indexOfPerc := int(math.Ceil(((perc / 100.0) * float64(length)) + 0.5))
if indexOfPerc >= length {
indexOfPerc = length - 1
}
return arr[indexOfPerc]
}
type Stats struct {
Prefix string
Count int
Avg time.Duration
P95 time.Duration
P99 time.Duration
}
func (s *Stats) String() string {
p95Ms := s.P95.Seconds() * 1000
p99Ms := s.P99.Seconds() * 1000
avgMs := s.Avg.Seconds() * 1000
return fmt.Sprintf("%s finished %d - 99th: %.02fms - 95th: %.02fms - avg: %.02fms",
s.Prefix, s.Count, p99Ms, p95Ms, avgMs)
}
func (m *TimerMetrics) getStats() *Stats {
var total time.Duration
for _, v := range m.timings {
total += v
}
// make a copy of timings so we still rotate through in order
timings := make(durations, len(m.timings))
copy(timings, m.timings)
sort.Sort(timings)
var avg time.Duration
if len(timings) > 0 {
avg = total / time.Duration(len(m.timings))
}
return &Stats{
Prefix: m.prefix,
Count: len(m.timings),
Avg: avg,
P95: percentile(95.0, timings),
P99: percentile(99.0, timings),
}
}
// get the current Stats
func (m *TimerMetrics) Stats() *Stats {
m.Lock()
s := m.getStats()
m.Unlock()
return s
}
// record a delta from time.Now()
func (m *TimerMetrics) Status(startTime time.Time) {
if m.statusEvery == 0 {
return
}
m.StatusDuration(time.Now().Sub(startTime))
}
// Record a duration, printing out stats every statusEvery interval
func (m *TimerMetrics) StatusDuration(duration time.Duration) {
if m.statusEvery == 0 {
return
}
m.Lock()
m.position++
var looped bool
if m.position > m.statusEvery {
// loop back around
looped = true
m.position = 1
}
if m.position > len(m.timings) {
m.timings = append(m.timings, duration)
} else {
m.timings[m.position-1] = duration
}
if !looped {
m.Unlock()
return
}
stats := m.getStats()
m.Unlock()
log.Printf("%s", stats)
}