/
smav.go
135 lines (117 loc) · 2.89 KB
/
smav.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
/*
Package variant provides some (well, at the time of this writing)
useful implementations of the expvar.Var interface (which is just a
Stringer).
*/
package variant
import (
"container/ring"
"expvar"
"fmt"
"math"
"sort"
"sync"
)
// represents a size bounded simple moving average
// it is thread/goroutine safe
type SimpleMovingStat struct {
size int
mutex *sync.Mutex
values *ring.Ring
calculate func(*SimpleMovingStat) float64
}
// Create a new simple moving median expvar.Var. It will be
// published under `name` and maintain `size` values for
// calculating the median.
//
// An empty name will cause it to not be published
//
// This is just a convenience helper for a SimpleMovingPercentile
func NewSimpleMovingMedian(name string, size int) *SimpleMovingStat {
return NewSimpleMovingPercentile(name, 0.50, size)
}
// Create a new simple moving percentile expvar.Var. It will be
// published under `name` and maintain `size` values for
// calculating the percentile.
//
// percentile must be between 0 and 1
//
// An empty name will cause it to not be published
func NewSimpleMovingPercentile(name string, percentile float64, size int) *SimpleMovingStat {
sm := new(SimpleMovingStat)
sm.size = size
sm.mutex = new(sync.Mutex)
sm.values = ring.New(size)
sm.calculate = func(s *SimpleMovingStat) float64 {
ary := make([]float64, 0)
s.values.Do(func(val interface{}) {
if val != nil {
ary = append(ary, val.(float64))
}
})
length := len(ary)
if length == 0 {
return 0.0
}
sort.Float64s(ary)
mid := int(float64(len(ary)) * percentile)
return ary[mid]
}
if name != "" {
expvar.Publish(name, sm)
}
return sm
}
// Create a new simple moving average expvar.Var. It will be
// published under `name` and maintain `size` values for
// calculating the average.
//
// An empty name will cause it to not be published
func NewSimpleMovingAverage(name string, size int) *SimpleMovingStat {
sma := new(SimpleMovingStat)
sma.size = size
sma.mutex = new(sync.Mutex)
sma.values = ring.New(size)
sma.calculate = func(s *SimpleMovingStat) float64 {
var sum float64 = 0.0
var cnt int = 0
s.values.Do(func(val interface{}) {
if val != nil {
cnt++
sum = sum + val.(float64)
}
})
return sum / float64(cnt)
}
if name != "" {
expvar.Publish(name, sma)
}
return sma
}
// display the value as a string
func (s *SimpleMovingStat) String() string {
v := s.Value()
if math.IsNaN(v) {
return `"NaN"`
}
if math.IsInf(v, 1) {
return `"+Infinity"`
}
if math.IsInf(v, -1) {
return `"-Infinity"`
}
return fmt.Sprintf("%f", s.Value())
}
// Append a new value to the stat
func (s *SimpleMovingStat) Update(val float64) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.values.Value = val
s.values = s.values.Next()
}
// obtain the current value
func (s *SimpleMovingStat) Value() float64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.calculate(s)
}