healthping_result.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package router
  2. import (
  3. "math"
  4. "time"
  5. )
  6. // HealthPingStats is the statistics of HealthPingRTTS
  7. type HealthPingStats struct {
  8. All int
  9. Fail int
  10. Deviation time.Duration
  11. Average time.Duration
  12. Max time.Duration
  13. Min time.Duration
  14. }
  15. // HealthPingRTTS holds ping rtts for health Checker
  16. type HealthPingRTTS struct {
  17. idx int
  18. cap int
  19. validity time.Duration
  20. rtts []*pingRTT
  21. lastUpdateAt time.Time
  22. stats *HealthPingStats
  23. }
  24. type pingRTT struct {
  25. time time.Time
  26. value time.Duration
  27. }
  28. // NewHealthPingResult returns a *HealthPingResult with specified capacity
  29. func NewHealthPingResult(cap int, validity time.Duration) *HealthPingRTTS {
  30. return &HealthPingRTTS{cap: cap, validity: validity}
  31. }
  32. // Get gets statistics of the HealthPingRTTS
  33. func (h *HealthPingRTTS) Get() *HealthPingStats {
  34. return h.getStatistics()
  35. }
  36. // GetWithCache get statistics and write cache for next call
  37. // Make sure use Mutex.Lock() before calling it, RWMutex.RLock()
  38. // is not an option since it writes cache
  39. func (h *HealthPingRTTS) GetWithCache() *HealthPingStats {
  40. lastPutAt := h.rtts[h.idx].time
  41. now := time.Now()
  42. if h.stats == nil || h.lastUpdateAt.Before(lastPutAt) || h.findOutdated(now) >= 0 {
  43. h.stats = h.getStatistics()
  44. h.lastUpdateAt = now
  45. }
  46. return h.stats
  47. }
  48. // Put puts a new rtt to the HealthPingResult
  49. func (h *HealthPingRTTS) Put(d time.Duration) {
  50. if h.rtts == nil {
  51. h.rtts = make([]*pingRTT, h.cap)
  52. for i := 0; i < h.cap; i++ {
  53. h.rtts[i] = &pingRTT{}
  54. }
  55. h.idx = -1
  56. }
  57. h.idx = h.calcIndex(1)
  58. now := time.Now()
  59. h.rtts[h.idx].time = now
  60. h.rtts[h.idx].value = d
  61. }
  62. func (h *HealthPingRTTS) calcIndex(step int) int {
  63. idx := h.idx
  64. idx += step
  65. if idx >= h.cap {
  66. idx %= h.cap
  67. }
  68. return idx
  69. }
  70. func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
  71. stats := &HealthPingStats{}
  72. stats.Fail = 0
  73. stats.Max = 0
  74. stats.Min = rttFailed
  75. sum := time.Duration(0)
  76. cnt := 0
  77. validRTTs := make([]time.Duration, 0)
  78. for _, rtt := range h.rtts {
  79. switch {
  80. case rtt.value == 0 || time.Since(rtt.time) > h.validity:
  81. continue
  82. case rtt.value == rttFailed:
  83. stats.Fail++
  84. continue
  85. }
  86. cnt++
  87. sum += rtt.value
  88. validRTTs = append(validRTTs, rtt.value)
  89. if stats.Max < rtt.value {
  90. stats.Max = rtt.value
  91. }
  92. if stats.Min > rtt.value {
  93. stats.Min = rtt.value
  94. }
  95. }
  96. stats.All = cnt + stats.Fail
  97. if cnt == 0 {
  98. stats.Min = 0
  99. return stats
  100. }
  101. stats.Average = time.Duration(int(sum) / cnt)
  102. var std float64
  103. if cnt < 2 {
  104. // no enough data for standard deviation, we assume it's half of the average rtt
  105. // if we don't do this, standard deviation of 1 round tested nodes is 0, will always
  106. // selected before 2 or more rounds tested nodes
  107. std = float64(stats.Average / 2)
  108. } else {
  109. variance := float64(0)
  110. for _, rtt := range validRTTs {
  111. variance += math.Pow(float64(rtt-stats.Average), 2)
  112. }
  113. std = math.Sqrt(variance / float64(cnt))
  114. }
  115. stats.Deviation = time.Duration(std)
  116. return stats
  117. }
  118. func (h *HealthPingRTTS) findOutdated(now time.Time) int {
  119. for i := h.cap - 1; i < 2*h.cap; i++ {
  120. // from oldest to latest
  121. idx := h.calcIndex(i)
  122. validity := h.rtts[idx].time.Add(h.validity)
  123. if h.lastUpdateAt.After(validity) {
  124. return idx
  125. }
  126. if validity.Before(now) {
  127. return idx
  128. }
  129. }
  130. return -1
  131. }