Skip to content

Commit 7170326

Browse files
improve docs
add tests
1 parent d0ccb2c commit 7170326

8 files changed

+96
-8
lines changed
219 KB
Binary file not shown.

docs/content/_index.md

+3
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,6 @@ dialOption := grpc.WithUnaryInterceptor(
152152
),
153153
)
154154
```
155+
156+
---
157+
[References used can be found here](_references.md)

docs/content/_references.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# References Used
2+
1. Original Java implementation - Netflix - https://github.com/netflix/concurrency-limits/
3+
1. Windowless Moving Percentile - Martin Jambon - https://mjambon.com/2016-07-23-moving-percentile/

measurements/moving_average.go

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"sync"
77
)
88

9+
// SimpleExponentialMovingAverage implements a simple exponential moving average
10+
// this implementation only uses a single alpha value to determine warm-up time and provides a mean
11+
// approximation
912
type SimpleExponentialMovingAverage struct {
1013
alpha float64
1114
initialAlpha float64
@@ -17,6 +20,7 @@ type SimpleExponentialMovingAverage struct {
1720
mu sync.RWMutex
1821
}
1922

23+
// NewSimpleExponentialMovingAverage creates a new simple moving average
2024
func NewSimpleExponentialMovingAverage(
2125
alpha float64,
2226
) (*SimpleExponentialMovingAverage, error) {
@@ -31,6 +35,8 @@ func NewSimpleExponentialMovingAverage(
3135
}, nil
3236
}
3337

38+
// Add a single sample and update the internal state.
39+
// returns true if the internal state was updated, also return the current value.
3440
func (m *SimpleExponentialMovingAverage) Add(value float64) (float64, bool) {
3541
m.mu.Lock()
3642
defer m.mu.Unlock()
@@ -56,12 +62,14 @@ func (m *SimpleExponentialMovingAverage) add(value float64) (float64, bool) {
5662
return m.value, changed
5763
}
5864

65+
// Get the current value.
5966
func (m *SimpleExponentialMovingAverage) Get() float64 {
6067
m.mu.RLock()
6168
defer m.mu.RUnlock()
6269
return m.value
6370
}
6471

72+
// Reset the internal state as if no samples were ever added.
6573
func (m *SimpleExponentialMovingAverage) Reset() {
6674
m.mu.Lock()
6775
m.seenSamples = 0
@@ -70,6 +78,7 @@ func (m *SimpleExponentialMovingAverage) Reset() {
7078
m.mu.Unlock()
7179
}
7280

81+
// Update will update the value given an operation function
7382
func (m *SimpleExponentialMovingAverage) Update(operation func(value float64) float64) {
7483
m.mu.Lock()
7584
defer m.mu.Unlock()

measurements/moving_average_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package measurements
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSimpleExponentialMovingAverage(t *testing.T) {
10+
t.Parallel()
11+
asrt := assert.New(t)
12+
m, err := NewSimpleExponentialMovingAverage(0.05)
13+
asrt.NoError(err)
14+
asrt.NotNil(m)
15+
16+
asrt.Equal(float64(0), m.Get())
17+
m.Add(10)
18+
asrt.Equal(float64(10), m.Get())
19+
m.Add(11)
20+
asrt.Equal(float64(10.5), m.Get())
21+
m.Add(11)
22+
m.Add(11)
23+
asrt.Equal(float64(10.75), m.Get())
24+
}

measurements/moving_variance.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import (
55
"sync"
66
)
77

8+
// SimpleMovingVariance implements a simple moving variance calculation based on the simple moving average.
89
type SimpleMovingVariance struct {
910
average *SimpleExponentialMovingAverage
1011
variance *SimpleExponentialMovingAverage
1112

1213
stdev float64 // square root of the estimated variance
13-
normalized float64 // (signal - mean) / stdev
14+
normalized float64 // (input - mean) / stdev
1415

1516
mu sync.RWMutex
1617
}
1718

19+
// NewSimpleMovingVariance will create a new exponential moving variance approximation based on the SimpleMovingAverage
1820
func NewSimpleMovingVariance(
1921
alphaAverage float64,
2022
alphaVariance float64,
@@ -33,6 +35,8 @@ func NewSimpleMovingVariance(
3335
}, nil
3436
}
3537

38+
// Add a single sample and update the internal state.
39+
// returns true if the internal state was updated, also return the current value.
3640
func (m *SimpleMovingVariance) Add(value float64) (float64, bool) {
3741
m.mu.Lock()
3842
defer m.mu.Unlock()
@@ -50,7 +54,7 @@ func (m *SimpleMovingVariance) Add(value float64) (float64, bool) {
5054
// edge case
5155
normalized = (value - mean) / stdev
5256
}
53-
//fmt.Printf("\tMV add: value=%0.5f mean=%0.5f variance=%0.5f stdev=%0.5f normalized=%0.5f\n", value, mean, variance, stdev, normalized)
57+
5458
if stdev != m.stdev || normalized != m.normalized {
5559
changed = true
5660
}
@@ -59,12 +63,14 @@ func (m *SimpleMovingVariance) Add(value float64) (float64, bool) {
5963
return stdev, changed
6064
}
6165

66+
// Get the current value.
6267
func (m *SimpleMovingVariance) Get() float64 {
6368
m.mu.RLock()
6469
defer m.mu.RUnlock()
6570
return m.variance.Get()
6671
}
6772

73+
// Reset the internal state as if no samples were ever added.
6874
func (m *SimpleMovingVariance) Reset() {
6975
m.mu.Lock()
7076
m.average.Reset()
@@ -74,6 +80,7 @@ func (m *SimpleMovingVariance) Reset() {
7480
m.mu.Unlock()
7581
}
7682

83+
// Update will update the value given an operation function
7784
func (m *SimpleMovingVariance) Update(operation func(value float64) float64) {
7885
m.mu.Lock()
7986
defer m.mu.Unlock()

measurements/moving_variance_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package measurements
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSimpleMovingVariance(t *testing.T) {
10+
t.Parallel()
11+
asrt := assert.New(t)
12+
m, err := NewSimpleMovingVariance(0.05, 0.05)
13+
asrt.NoError(err)
14+
asrt.NotNil(m)
15+
16+
// initial condition
17+
asrt.Equal(float64(0), m.Get())
18+
// warmup first sample
19+
m.Add(10)
20+
asrt.Equal(float64(0), m.Get())
21+
// first variance reading is expected to be 1 here
22+
m.Add(11)
23+
asrt.Equal(float64(1), m.Get())
24+
m.Add(10)
25+
m.Add(11)
26+
asrt.InDelta(float64(0.5648), m.Get(), 0.00005)
27+
m.Add(20)
28+
m.Add(100)
29+
m.Add(30)
30+
asrt.InDelta(float64(1295.7841), m.Get(), 0.00005)
31+
}

measurements/windowless_moving_percentile.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import (
55
"sync"
66
)
77

8-
// Much credit goes to here: https://mjambon.com/2016-07-23-moving-percentile/
9-
8+
// WindowlessMovingPercentile implements a moving percentile.
9+
// This implementation uses a windowless calculation that while not strictly always accurate,
10+
// provides a very close estimation in O(1) time and space.
11+
// Much credit goes to Martin Jambon here: https://mjambon.com/2016-07-23-moving-percentile/
12+
// a copy can be found in github.com/platinummonkey/go-concurrency-limits/docs/assets/moving_percentile_reference.pdf
13+
// and this is a port of the OCaml implementation provided in that reference.
1014
type WindowlessMovingPercentile struct {
1115
p float64
1216
deltaInitial float64
@@ -21,9 +25,16 @@ type WindowlessMovingPercentile struct {
2125
mu sync.RWMutex
2226
}
2327

28+
// NewWindowlessMovingPercentile creates a new Windowless Moving Percentile
29+
// p - percentile requested, accepts (0,1)
30+
// deltaInitial - the initial delta value, here 0 is acceptable if you expect it to be rather stable at start, otherwise
31+
// choose a larger value. This would be estimated: `delta := stdev * r` where `r` is a user chosen
32+
// constant. Good values are generally from 0.001 to 0.01
33+
// movingAvgAlphaAvg - this is the alpha value for the simple moving average. A good start is 0.05. Accepts [0,1]
34+
// movingVarianceAlphaAvg - this is the alpha value for the simple moving variance. A good start is 0.05. Accepts [0,1]
2435
func NewWindowlessMovingPercentile(
25-
p float64,
26-
delta float64,
36+
p float64, // percentile requested
37+
deltaInitial float64,
2738
movingAvgAlphaAvg float64,
2839
movingVarianceAlphaVar float64,
2940
) (*WindowlessMovingPercentile, error) {
@@ -42,8 +53,8 @@ func NewWindowlessMovingPercentile(
4253
return &WindowlessMovingPercentile{
4354
p: p,
4455
q: q,
45-
deltaInitial: delta,
46-
delta: delta,
56+
deltaInitial: deltaInitial,
57+
delta: deltaInitial,
4758
deltaState: variance,
4859
}, nil
4960
}

0 commit comments

Comments
 (0)