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
|
// Implements a data structure for quality attenuation.
package qualityattenuation
import (
"math"
"github.com/influxdata/tdigest"
)
type SimpleQualityAttenuation struct {
EmpiricalDistribution *tdigest.TDigest
Offset float64
Offset_sum float64
Offset_sum_of_squares float64
Number_of_samples int64
Number_of_losses int64
Latency_eq_loss_threshold float64
Minimum_latency float64
Maximum_latency float64
}
func NewSimpleQualityAttenuation() *SimpleQualityAttenuation {
return &SimpleQualityAttenuation{
EmpiricalDistribution: tdigest.NewWithCompression(50),
Offset: 0.1,
Offset_sum: 0.0,
Offset_sum_of_squares: 0.0,
Number_of_samples: 0,
Number_of_losses: 0,
Latency_eq_loss_threshold: 15.0, // Count latency greater than this value as a loss.
Minimum_latency: 0.0,
Maximum_latency: 0.0,
}
}
func (qa *SimpleQualityAttenuation) AddSample(sample float64) {
if sample <= 0.0 {
// Ignore zero or negative samples because they cannot be valid.
// TODO: This should raise a warning and/or trigger error handling.
return
}
qa.Number_of_samples++
if sample > qa.Latency_eq_loss_threshold {
qa.Number_of_losses++
return
} else {
if qa.Minimum_latency == 0.0 || sample < qa.Minimum_latency {
qa.Minimum_latency = sample
}
if qa.Maximum_latency == 0.0 || sample > qa.Maximum_latency {
qa.Maximum_latency = sample
}
qa.EmpiricalDistribution.Add(sample, 1)
qa.Offset_sum += sample - qa.Offset
qa.Offset_sum_of_squares += (sample - qa.Offset) * (sample - qa.Offset)
}
}
func (qa *SimpleQualityAttenuation) GetNumberOfLosses() int64 {
return qa.Number_of_losses
}
func (qa *SimpleQualityAttenuation) GetNumberOfSamples() int64 {
return qa.Number_of_samples
}
func (qa *SimpleQualityAttenuation) GetPercentile(percentile float64) float64 {
return qa.EmpiricalDistribution.Quantile(percentile / 100)
}
func (qa *SimpleQualityAttenuation) GetAverage() float64 {
return qa.Offset_sum/float64(qa.Number_of_samples-qa.Number_of_losses) + qa.Offset
}
func (qa *SimpleQualityAttenuation) GetVariance() float64 {
number_of_latency_samples := float64(qa.Number_of_samples) - float64(qa.Number_of_losses)
return (qa.Offset_sum_of_squares - (qa.Offset_sum * qa.Offset_sum / number_of_latency_samples)) / (number_of_latency_samples)
}
func (qa *SimpleQualityAttenuation) GetStandardDeviation() float64 {
return math.Sqrt(qa.GetVariance())
}
func (qa *SimpleQualityAttenuation) GetMinimum() float64 {
return qa.Minimum_latency
}
func (qa *SimpleQualityAttenuation) GetMaximum() float64 {
return qa.Maximum_latency
}
func (qa *SimpleQualityAttenuation) GetMedian() float64 {
return qa.GetPercentile(50.0)
}
func (qa *SimpleQualityAttenuation) GetLossPercentage() float64 {
return 100 * float64(qa.Number_of_losses) / float64(qa.Number_of_samples)
}
func (qa *SimpleQualityAttenuation) GetRPM() float64 {
return 60.0 / qa.GetAverage()
}
func (qa *SimpleQualityAttenuation) GetPDV(percentile float64) float64 {
return qa.GetPercentile(percentile) - qa.GetMinimum()
}
// Merge two quality attenuation values. This operation assumes the two samples have the same offset and latency_eq_loss_threshold, and
// will return an error if they do not.
// It also assumes that the two quality attenuation values are measurements of the same thing (path, outcome, etc.).
func (qa *SimpleQualityAttenuation) Merge(other *SimpleQualityAttenuation) {
// Check that offsets are the same
if qa.Offset != other.Offset ||
qa.Latency_eq_loss_threshold != other.Latency_eq_loss_threshold {
//"Cannot merge quality attenuation values with different offset or latency_eq_loss_threshold"
}
for _, centroid := range other.EmpiricalDistribution.Centroids() {
mean := centroid.Mean
weight := centroid.Weight
qa.EmpiricalDistribution.Add(mean, weight)
}
qa.Offset_sum += other.Offset_sum
qa.Offset_sum_of_squares += other.Offset_sum_of_squares
qa.Number_of_samples += other.Number_of_samples
qa.Number_of_losses += other.Number_of_losses
if other.Minimum_latency < qa.Minimum_latency {
qa.Minimum_latency = other.Minimum_latency
}
if other.Maximum_latency > qa.Maximum_latency {
qa.Maximum_latency = other.Maximum_latency
}
}
|