ewma_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package maths
  4. import (
  5. "slices"
  6. "testing"
  7. "time"
  8. )
  9. // some real world latency samples.
  10. var (
  11. latencyHistory1 = []int{
  12. 14, 12, 15, 6, 19, 12, 13, 13, 13, 16, 17, 11, 17, 11, 14, 15, 14, 15,
  13. 16, 16, 17, 14, 12, 16, 18, 14, 14, 11, 15, 15, 25, 11, 15, 14, 12, 15,
  14. 13, 12, 13, 15, 11, 13, 15, 14, 14, 15, 12, 15, 18, 12, 15, 22, 12, 13,
  15. 10, 14, 16, 15, 16, 11, 14, 17, 18, 20, 16, 11, 16, 14, 5, 15, 17, 12,
  16. 15, 11, 15, 20, 12, 17, 12, 17, 15, 12, 12, 11, 14, 15, 11, 20, 14, 13,
  17. 11, 12, 13, 13, 11, 13, 11, 15, 13, 13, 14, 12, 11, 12, 12, 14, 11, 13,
  18. 12, 12, 12, 19, 14, 13, 13, 14, 11, 12, 10, 11, 15, 12, 14, 11, 11, 14,
  19. 14, 12, 12, 11, 14, 12, 11, 12, 14, 11, 12, 15, 12, 14, 12, 12, 21, 16,
  20. 21, 12, 16, 9, 11, 16, 14, 13, 14, 12, 13, 16,
  21. }
  22. latencyHistory2 = []int{
  23. 18, 20, 21, 21, 20, 23, 18, 18, 20, 21, 20, 19, 22, 18, 20, 20, 19, 21,
  24. 21, 22, 22, 19, 18, 22, 22, 19, 20, 17, 16, 11, 25, 16, 18, 21, 17, 22,
  25. 19, 18, 22, 21, 20, 18, 22, 17, 17, 20, 19, 10, 19, 16, 19, 25, 17, 18,
  26. 15, 20, 21, 20, 23, 22, 22, 22, 19, 22, 22, 17, 22, 20, 20, 19, 21, 22,
  27. 20, 19, 17, 22, 16, 16, 20, 22, 17, 19, 21, 16, 20, 22, 19, 21, 20, 19,
  28. 13, 14, 23, 19, 16, 10, 19, 15, 15, 17, 16, 18, 14, 16, 18, 22, 20, 18,
  29. 18, 21, 15, 19, 18, 19, 18, 20, 17, 19, 21, 19, 20, 19, 20, 20, 17, 14,
  30. 17, 17, 18, 21, 20, 18, 18, 17, 16, 17, 17, 20, 22, 19, 20, 21, 21, 20,
  31. 21, 24, 20, 18, 12, 17, 18, 17, 19, 19, 19,
  32. }
  33. )
  34. func TestEWMALatencyHistory(t *testing.T) {
  35. type result struct {
  36. t time.Time
  37. v float64
  38. s int
  39. }
  40. for _, latencyHistory := range [][]int{latencyHistory1, latencyHistory2} {
  41. startTime := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
  42. halfLife := 30.0
  43. ewma := NewEWMA(halfLife)
  44. var results []result
  45. sum := 0.0
  46. for i, latency := range latencyHistory {
  47. t := startTime.Add(time.Duration(i) * time.Second)
  48. ewma.Update(float64(latency), t)
  49. sum += float64(latency)
  50. results = append(results, result{t, ewma.Get(), latency})
  51. }
  52. mean := sum / float64(len(latencyHistory))
  53. min := float64(slices.Min(latencyHistory))
  54. max := float64(slices.Max(latencyHistory))
  55. t.Logf("EWMA Latency History (half-life: %.1f seconds):", halfLife)
  56. t.Logf("Mean latency: %.2f ms", mean)
  57. t.Logf("Range: [%.1f, %.1f]", min, max)
  58. t.Log("Samples: ")
  59. sparkline := []rune("▁▂▃▄▅▆▇█")
  60. var sampleLine []rune
  61. for _, r := range results {
  62. idx := int(((float64(r.s) - min) / (max - min)) * float64(len(sparkline)-1))
  63. if idx >= len(sparkline) {
  64. idx = len(sparkline) - 1
  65. }
  66. sampleLine = append(sampleLine, sparkline[idx])
  67. }
  68. t.Log(string(sampleLine))
  69. t.Log("EWMA: ")
  70. var ewmaLine []rune
  71. for _, r := range results {
  72. idx := int(((r.v - min) / (max - min)) * float64(len(sparkline)-1))
  73. if idx >= len(sparkline) {
  74. idx = len(sparkline) - 1
  75. }
  76. ewmaLine = append(ewmaLine, sparkline[idx])
  77. }
  78. t.Log(string(ewmaLine))
  79. t.Log("")
  80. t.Logf("Time | Sample | Value | Value - Sample")
  81. t.Logf("")
  82. for _, result := range results {
  83. t.Logf("%10s | % 6d | % 5.2f | % 5.2f", result.t.Format("15:04:05"), result.s, result.v, result.v-float64(result.s))
  84. }
  85. // check that all results are greater than the min, and less than the max of the input,
  86. // and they're all close to the mean.
  87. for _, result := range results {
  88. if result.v < float64(min) || result.v > float64(max) {
  89. t.Errorf("result %f out of range [%f, %f]", result.v, min, max)
  90. }
  91. if result.v < mean*0.9 || result.v > mean*1.1 {
  92. t.Errorf("result %f not close to mean %f", result.v, mean)
  93. }
  94. }
  95. }
  96. }
  97. func TestHalfLife(t *testing.T) {
  98. start := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
  99. ewma := NewEWMA(30.0)
  100. ewma.Update(10, start)
  101. ewma.Update(0, start.Add(30*time.Second))
  102. if ewma.Get() != 5 {
  103. t.Errorf("expected 5, got %f", ewma.Get())
  104. }
  105. ewma.Update(10, start.Add(60*time.Second))
  106. if ewma.Get() != 7.5 {
  107. t.Errorf("expected 7.5, got %f", ewma.Get())
  108. }
  109. ewma.Update(10, start.Add(90*time.Second))
  110. if ewma.Get() != 8.75 {
  111. t.Errorf("expected 8.75, got %f", ewma.Get())
  112. }
  113. }
  114. func TestZeroValue(t *testing.T) {
  115. start := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
  116. var ewma EWMA
  117. ewma.Update(10, start)
  118. ewma.Update(0, start.Add(time.Second))
  119. if ewma.Get() != 5 {
  120. t.Errorf("expected 5, got %f", ewma.Get())
  121. }
  122. ewma.Update(10, start.Add(2*time.Second))
  123. if ewma.Get() != 7.5 {
  124. t.Errorf("expected 7.5, got %f", ewma.Get())
  125. }
  126. ewma.Update(10, start.Add(3*time.Second))
  127. if ewma.Get() != 8.75 {
  128. t.Errorf("expected 8.75, got %f", ewma.Get())
  129. }
  130. }
  131. func TestReset(t *testing.T) {
  132. start := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
  133. ewma := NewEWMA(30.0)
  134. ewma.Update(10, start)
  135. ewma.Update(0, start.Add(30*time.Second))
  136. if ewma.Get() != 5 {
  137. t.Errorf("expected 5, got %f", ewma.Get())
  138. }
  139. ewma.Reset()
  140. if ewma.Get() != 0 {
  141. t.Errorf("expected 0, got %f", ewma.Get())
  142. }
  143. ewma.Update(10, start.Add(90*time.Second))
  144. if ewma.Get() != 10 {
  145. t.Errorf("expected 10, got %f", ewma.Get())
  146. }
  147. }