ewma.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // Package ewma implements exponentially weighted moving averages.
  2. package ewma
  3. // Copyright (c) 2013 VividCortex, Inc. All rights reserved.
  4. // Please see the LICENSE file for applicable license terms.
  5. const (
  6. // By default, we average over a one-minute period, which means the average
  7. // age of the metrics in the period is 30 seconds.
  8. AVG_METRIC_AGE float64 = 30.0
  9. // The formula for computing the decay factor from the average age comes
  10. // from "Production and Operations Analysis" by Steven Nahmias.
  11. DECAY float64 = 2 / (float64(AVG_METRIC_AGE) + 1)
  12. // For best results, the moving average should not be initialized to the
  13. // samples it sees immediately. The book "Production and Operations
  14. // Analysis" by Steven Nahmias suggests initializing the moving average to
  15. // the mean of the first 10 samples. Until the VariableEwma has seen this
  16. // many samples, it is not "ready" to be queried for the value of the
  17. // moving average. This adds some memory cost.
  18. WARMUP_SAMPLES uint8 = 10
  19. )
  20. // MovingAverage is the interface that computes a moving average over a time-
  21. // series stream of numbers. The average may be over a window or exponentially
  22. // decaying.
  23. type MovingAverage interface {
  24. Add(float64)
  25. Value() float64
  26. Set(float64)
  27. }
  28. // NewMovingAverage constructs a MovingAverage that computes an average with the
  29. // desired characteristics in the moving window or exponential decay. If no
  30. // age is given, it constructs a default exponentially weighted implementation
  31. // that consumes minimal memory. The age is related to the decay factor alpha
  32. // by the formula given for the DECAY constant. It signifies the average age
  33. // of the samples as time goes to infinity.
  34. func NewMovingAverage(age ...float64) MovingAverage {
  35. if len(age) == 0 || age[0] == AVG_METRIC_AGE {
  36. return new(SimpleEWMA)
  37. }
  38. return &VariableEWMA{
  39. decay: 2 / (age[0] + 1),
  40. }
  41. }
  42. // A SimpleEWMA represents the exponentially weighted moving average of a
  43. // series of numbers. It WILL have different behavior than the VariableEWMA
  44. // for multiple reasons. It has no warm-up period and it uses a constant
  45. // decay. These properties let it use less memory. It will also behave
  46. // differently when it's equal to zero, which is assumed to mean
  47. // uninitialized, so if a value is likely to actually become zero over time,
  48. // then any non-zero value will cause a sharp jump instead of a small change.
  49. // However, note that this takes a long time, and the value may just
  50. // decays to a stable value that's close to zero, but which won't be mistaken
  51. // for uninitialized. See http://play.golang.org/p/litxBDr_RC for example.
  52. type SimpleEWMA struct {
  53. // The current value of the average. After adding with Add(), this is
  54. // updated to reflect the average of all values seen thus far.
  55. value float64
  56. }
  57. // Add adds a value to the series and updates the moving average.
  58. func (e *SimpleEWMA) Add(value float64) {
  59. if e.value == 0 { // this is a proxy for "uninitialized"
  60. e.value = value
  61. } else {
  62. e.value = (value * DECAY) + (e.value * (1 - DECAY))
  63. }
  64. }
  65. // Value returns the current value of the moving average.
  66. func (e *SimpleEWMA) Value() float64 {
  67. return e.value
  68. }
  69. // Set sets the EWMA's value.
  70. func (e *SimpleEWMA) Set(value float64) {
  71. e.value = value
  72. }
  73. // VariableEWMA represents the exponentially weighted moving average of a series of
  74. // numbers. Unlike SimpleEWMA, it supports a custom age, and thus uses more memory.
  75. type VariableEWMA struct {
  76. // The multiplier factor by which the previous samples decay.
  77. decay float64
  78. // The current value of the average.
  79. value float64
  80. // The number of samples added to this instance.
  81. count uint8
  82. }
  83. // Add adds a value to the series and updates the moving average.
  84. func (e *VariableEWMA) Add(value float64) {
  85. switch {
  86. case e.count < WARMUP_SAMPLES:
  87. e.count++
  88. e.value += value
  89. case e.count == WARMUP_SAMPLES:
  90. e.count++
  91. e.value = e.value / float64(WARMUP_SAMPLES)
  92. e.value = (value * e.decay) + (e.value * (1 - e.decay))
  93. default:
  94. e.value = (value * e.decay) + (e.value * (1 - e.decay))
  95. }
  96. }
  97. // Value returns the current value of the average, or 0.0 if the series hasn't
  98. // warmed up yet.
  99. func (e *VariableEWMA) Value() float64 {
  100. if e.count <= WARMUP_SAMPLES {
  101. return 0.0
  102. }
  103. return e.value
  104. }
  105. // Set sets the EWMA's value.
  106. func (e *VariableEWMA) Set(value float64) {
  107. e.value = value
  108. if e.count <= WARMUP_SAMPLES {
  109. e.count = WARMUP_SAMPLES + 1
  110. }
  111. }