summaryrefslogtreecommitdiff
path: root/qualityattenuation/qualityattenuation.go
blob: 980253f93601645dc6aef78036bb4c126600b086 (plain)
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
	}
}