| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- package burst
- import (
- "math"
- "time"
- )
- // HealthPingStats is the statistics of HealthPingRTTS
- type HealthPingStats struct {
- All int
- Fail int
- Deviation time.Duration
- Average time.Duration
- Max time.Duration
- Min time.Duration
- }
- // HealthPingRTTS holds ping rtts for health Checker
- type HealthPingRTTS struct {
- idx int
- cap int
- validity time.Duration
- rtts []*pingRTT
- lastUpdateAt time.Time
- stats *HealthPingStats
- }
- type pingRTT struct {
- time time.Time
- value time.Duration
- }
- // NewHealthPingResult returns a *HealthPingResult with specified capacity
- func NewHealthPingResult(cap int, validity time.Duration) *HealthPingRTTS {
- return &HealthPingRTTS{cap: cap, validity: validity}
- }
- // Get gets statistics of the HealthPingRTTS
- func (h *HealthPingRTTS) Get() *HealthPingStats {
- return h.getStatistics()
- }
- // GetWithCache get statistics and write cache for next call
- // Make sure use Mutex.Lock() before calling it, RWMutex.RLock()
- // is not an option since it writes cache
- func (h *HealthPingRTTS) GetWithCache() *HealthPingStats {
- lastPutAt := h.rtts[h.idx].time
- now := time.Now()
- if h.stats == nil || h.lastUpdateAt.Before(lastPutAt) || h.findOutdated(now) >= 0 {
- h.stats = h.getStatistics()
- h.lastUpdateAt = now
- }
- return h.stats
- }
- // Put puts a new rtt to the HealthPingResult
- func (h *HealthPingRTTS) Put(d time.Duration) {
- if h.rtts == nil {
- h.rtts = make([]*pingRTT, h.cap)
- for i := 0; i < h.cap; i++ {
- h.rtts[i] = &pingRTT{}
- }
- h.idx = -1
- }
- h.idx = h.calcIndex(1)
- now := time.Now()
- h.rtts[h.idx].time = now
- h.rtts[h.idx].value = d
- }
- func (h *HealthPingRTTS) calcIndex(step int) int {
- idx := h.idx
- idx += step
- if idx >= h.cap {
- idx %= h.cap
- }
- return idx
- }
- func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
- stats := &HealthPingStats{}
- stats.Fail = 0
- stats.Max = 0
- stats.Min = rttFailed
- sum := time.Duration(0)
- cnt := 0
- validRTTs := make([]time.Duration, 0)
- for _, rtt := range h.rtts {
- switch {
- case rtt.value == 0 || time.Since(rtt.time) > h.validity:
- continue
- case rtt.value == rttFailed:
- stats.Fail++
- continue
- }
- cnt++
- sum += rtt.value
- validRTTs = append(validRTTs, rtt.value)
- if stats.Max < rtt.value {
- stats.Max = rtt.value
- }
- if stats.Min > rtt.value {
- stats.Min = rtt.value
- }
- }
- stats.All = cnt + stats.Fail
- if cnt == 0 {
- stats.Min = 0
- return stats
- }
- stats.Average = time.Duration(int(sum) / cnt)
- var std float64
- if cnt < 2 {
- // no enough data for standard deviation, we assume it's half of the average rtt
- // if we don't do this, standard deviation of 1 round tested nodes is 0, will always
- // selected before 2 or more rounds tested nodes
- std = float64(stats.Average / 2)
- } else {
- variance := float64(0)
- for _, rtt := range validRTTs {
- variance += math.Pow(float64(rtt-stats.Average), 2)
- }
- std = math.Sqrt(variance / float64(cnt))
- }
- stats.Deviation = time.Duration(std)
- return stats
- }
- func (h *HealthPingRTTS) findOutdated(now time.Time) int {
- for i := h.cap - 1; i < 2*h.cap; i++ {
- // from oldest to latest
- idx := h.calcIndex(i)
- validity := h.rtts[idx].time.Add(h.validity)
- if h.lastUpdateAt.After(validity) {
- return idx
- }
- if validity.Before(now) {
- return idx
- }
- }
- return -1
- }
|