summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Ivar Teigen <[email protected]>2023-05-19 13:38:52 +0200
committerBjørn Ivar Teigen <[email protected]>2023-05-19 13:38:52 +0200
commit1dc9c660ca8829bd386688a56b7047644789eb9a (patch)
treee539df8859290b50556a39af1efd2a35efa0f548
parenta0e0b1861d5b2d0d77e728042c915f5b7742e744 (diff)
Implemented basic Quality Attenuation
-rw-r--r--go.mod4
-rw-r--r--go.sum7
-rw-r--r--networkQuality.go37
-rw-r--r--qualityattenuation/qualityattenuation.go133
4 files changed, 181 insertions, 0 deletions
diff --git a/go.mod b/go.mod
index a448c75..49590e5 100644
--- a/go.mod
+++ b/go.mod
@@ -11,3 +11,7 @@ require (
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/text v0.7.0 // indirect
)
+
+require (
+ github.com/influxdata/tdigest v0.0.1
+) \ No newline at end of file
diff --git a/go.sum b/go.sum
index 3a4ddff..13f172f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY=
+github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
@@ -6,3 +10,6 @@ golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
diff --git a/networkQuality.go b/networkQuality.go
index 309aa94..676d98d 100644
--- a/networkQuality.go
+++ b/networkQuality.go
@@ -33,6 +33,7 @@ import (
"github.com/network-quality/goresponsiveness/lgc"
"github.com/network-quality/goresponsiveness/ms"
"github.com/network-quality/goresponsiveness/probe"
+ "github.com/network-quality/goresponsiveness/qualityattenuation"
"github.com/network-quality/goresponsiveness/rpm"
"github.com/network-quality/goresponsiveness/stabilizer"
"github.com/network-quality/goresponsiveness/timeoutat"
@@ -86,6 +87,11 @@ var (
false,
"Enable the collection and display of extended statistics -- may not be available on certain platforms.",
)
+ printQualityAttenuation = flag.Bool(
+ "quality-attenuation",
+ false,
+ "Print quality attenuation information.",
+ )
dataLoggerBaseFileName = flag.String(
"logger-filename",
"",
@@ -469,6 +475,7 @@ func main() {
probeStabilizer := stabilizer.NewProbeStabilizer(probeI, K, S, probeStabilizerDebugLevel, probeStabilizerDebugConfig)
selfRtts := ms.NewInfiniteMathematicalSeries[float64]()
+ selfRttsQualityAttenuation := qualityattenuation.NewSimpleQualityAttenuation()
foreignRtts := ms.NewInfiniteMathematicalSeries[float64]()
// For later debugging output, record the last throughputs on load-generating connectings
@@ -543,6 +550,7 @@ timeout:
}
} else if probeMeasurement.Type == probe.SelfDown || probeMeasurement.Type == probe.SelfUp {
selfRtts.AddElement(probeMeasurement.Duration.Seconds())
+ selfRttsQualityAttenuation.AddSample(probeMeasurement.Duration.Seconds())
}
if probeMeasurement.Type == probe.Foreign {
@@ -670,6 +678,35 @@ Trimmed Mean Foreign RTT: %f
)
}
+ if *printQualityAttenuation {
+ fmt.Println("Quality Attenuation Statistics:")
+ fmt.Printf(
+ `Number of losses: %d
+Number of samples: %d
+Loss: %f
+Min: %.6f
+Max: %.6f
+Mean: %.6f
+Variance: %.6f
+Standard Deviation: %.6f
+PDV(90): %.6f
+PDV(99): %.6f
+P(90): %.6f
+P(99): %.6f
+`, selfRttsQualityAttenuation.GetNumberOfLosses(),
+ selfRttsQualityAttenuation.GetNumberOfSamples(),
+ selfRttsQualityAttenuation.GetLossPercentage(),
+ selfRttsQualityAttenuation.GetMinimum(),
+ selfRttsQualityAttenuation.GetMaximum(),
+ selfRttsQualityAttenuation.GetAverage(),
+ selfRttsQualityAttenuation.GetVariance(),
+ selfRttsQualityAttenuation.GetStandardDeviation(),
+ selfRttsQualityAttenuation.GetPDV(90),
+ selfRttsQualityAttenuation.GetPDV(99),
+ selfRttsQualityAttenuation.GetPercentile(90),
+ selfRttsQualityAttenuation.GetPercentile(99))
+ }
+
if !testRanToStability {
fmt.Printf("Test did not run to stability, these results are estimates:\n")
}
diff --git a/qualityattenuation/qualityattenuation.go b/qualityattenuation/qualityattenuation.go
new file mode 100644
index 0000000..67143a7
--- /dev/null
+++ b/qualityattenuation/qualityattenuation.go
@@ -0,0 +1,133 @@
+// 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.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 - 1)
+}
+
+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 float64(qa.Number_of_losses) / float64(qa.Number_of_samples)
+}
+
+func (qa *SimpleQualityAttenuation) GetRPM() float64 {
+ return 60 / 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
+ }
+}