Hunter0x7c7
2022-08-11 b8230139fb40edea387617b6accd8371e37eda58
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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
}