summaryrefslogtreecommitdiff
path: root/qualityattenuation/qualityattenuation.go
diff options
context:
space:
mode:
Diffstat (limited to 'qualityattenuation/qualityattenuation.go')
-rw-r--r--qualityattenuation/qualityattenuation.go130
1 files changed, 130 insertions, 0 deletions
diff --git a/qualityattenuation/qualityattenuation.go b/qualityattenuation/qualityattenuation.go
index 279959e..c6a2f85 100644
--- a/qualityattenuation/qualityattenuation.go
+++ b/qualityattenuation/qualityattenuation.go
@@ -9,6 +9,39 @@ import (
"github.com/influxdata/tdigest"
)
+type cablelabsHist struct {
+ hist [256]float64
+}
+
+func (h *cablelabsHist) GetHistogram() [256]float64 {
+ return h.hist
+}
+
+func (h *cablelabsHist) AddSample(sample float64) error {
+ bin := 0
+ if sample < 0.050 {
+ // Round down
+ bin = int(sample / 0.0005)
+ h.hist[bin]++
+ } else if sample < 0.150 {
+ bin = int((sample - 0.050) / 0.001)
+ h.hist[100+bin]++
+ } else if sample < 1.150 {
+ bin = int((sample - 0.150) / 0.020)
+ h.hist[200+bin]++
+ } else if sample < 1.400 {
+ bin = 250
+ h.hist[bin]++
+ } else if sample < 3.000 {
+ bin = int((sample - 1.400) / 0.400)
+ h.hist[251+bin]++
+ } else {
+ bin = 255
+ h.hist[bin]++
+ }
+ return nil
+}
+
type SimpleQualityAttenuation struct {
empiricalDistribution *tdigest.TDigest
offset float64
@@ -21,6 +54,16 @@ type SimpleQualityAttenuation struct {
maximumLatency float64
}
+type percentileLatencyPair struct {
+ percentile float64
+ perfectLatency float64
+ uselessLatency float64
+}
+
+type qualityRequirement struct {
+ latencyRequirements []percentileLatencyPair
+}
+
func NewSimpleQualityAttenuation() *SimpleQualityAttenuation {
return &SimpleQualityAttenuation{
empiricalDistribution: tdigest.NewWithCompression(50),
@@ -108,6 +151,32 @@ func (qa *SimpleQualityAttenuation) GetPDV(percentile float64) float64 {
return qa.GetPercentile(percentile) - qa.GetMinimum()
}
+func (qa *SimpleQualityAttenuation) PrintCablelabsStatisticsSummary() string {
+ // Prints a digest based on Cablelabs Latency Measurements Metrics and Architeture, CL-TR-LM-Arch-V01-221123, https://www.cablelabs.com/specifications/CL-TR-LM-Arch
+ // The recommendation is to report the following percentiles: 0, 10, 25, 50, 75, 90, 95, 99, 99.9 and 100
+ return fmt.Sprintf("Cablelabs Statistics Summary:\n"+
+ "0th Percentile: %f\n"+
+ "10th Percentile: %f\n"+
+ "25th Percentile: %f\n"+
+ "50th Percentile: %f\n"+
+ "75th Percentile: %f\n"+
+ "90th Percentile: %f\n"+
+ "95th Percentile: %f\n"+
+ "99th Percentile: %f\n"+
+ "99.9th Percentile: %f\n"+
+ "100th Percentile: %f\n",
+ qa.GetPercentile(0.0),
+ qa.GetPercentile(10.0),
+ qa.GetPercentile(25.0),
+ qa.GetPercentile(50.0),
+ qa.GetPercentile(75.0),
+ qa.GetPercentile(90.0),
+ qa.GetPercentile(95.0),
+ qa.GetPercentile(99.0),
+ qa.GetPercentile(99.9),
+ qa.GetPercentile(100.0))
+}
+
// 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.).
@@ -134,3 +203,64 @@ func (qa *SimpleQualityAttenuation) Merge(other *SimpleQualityAttenuation) error
}
return nil
}
+
+func (qa *SimpleQualityAttenuation) EmpiricalDistributionHistogram() []float64 {
+ // Convert the tdigest to a histogram on the format defined by CableLabs, with the following bucket edges:
+ // 100 bins from 0 to 50 ms, each 0.5 ms wide
+ // 100 bins from 50 to 100 ms, each 1 ms wide
+ // 50 bins from 150 to 1150 ms, each 20 ms wide
+ // 1 bin from 1150 to 1400 ms, 250 ms wide
+ // 4 bins from 1400 to 3000 ms, each 400 ms wide
+ hist := make([]float64, 256)
+ for i := 0; i < 100; i++ {
+ hist[i] = float64(qa.numberOfSamples) * (qa.empiricalDistribution.CDF(float64(i+1)*0.0005) - qa.empiricalDistribution.CDF(float64(i)*0.0005))
+ }
+ for i := 100; i < 200; i++ {
+ hist[i] = float64(qa.numberOfSamples) * (qa.empiricalDistribution.CDF(0.050+float64(i-99)*0.001) - qa.empiricalDistribution.CDF(0.050+float64(i-100)*0.001))
+ }
+ for i := 200; i < 250; i++ {
+ hist[i] = float64(qa.numberOfSamples) * (qa.empiricalDistribution.CDF(0.150+float64(i-199)*0.020) - qa.empiricalDistribution.CDF(0.150+float64(i-200)*0.020))
+ }
+ for i := 250; i < 251; i++ {
+ hist[i] = float64(qa.numberOfSamples) * (qa.empiricalDistribution.CDF(1.150+0.250) - qa.empiricalDistribution.CDF(1.150))
+ }
+ for i := 251; i < 255; i++ {
+ hist[i] = float64(qa.numberOfSamples) * (qa.empiricalDistribution.CDF(1.400+float64(i-250)*0.400) - qa.empiricalDistribution.CDF(1.400+float64(i-251)*0.400))
+ }
+ hist[255] = float64(qa.numberOfSamples) * (1 - qa.empiricalDistribution.CDF(3.000))
+ return hist
+}
+
+// Compute the Quality of Outcome (QoO) for a given quality requirement.
+// The details and motivation for the QoO metric are described in the following internet draft:
+// https://datatracker.ietf.org/doc/draft-olden-ippm-qoo/
+func (qa *SimpleQualityAttenuation) QoO(requirement qualityRequirement) float64 {
+ QoO := 100.0
+ for _, percentileLatencyPair := range requirement.latencyRequirements {
+ score := 0.0
+ percentile := percentileLatencyPair.percentile
+ perfectLatency := percentileLatencyPair.perfectLatency
+ uselessLatency := percentileLatencyPair.uselessLatency
+ latency := qa.GetPercentile(percentile)
+ if latency >= uselessLatency {
+ score = 0.0
+ } else if latency <= perfectLatency {
+ score = 100.0
+ } else {
+ score = 100 * ((uselessLatency - latency) / (uselessLatency - perfectLatency))
+ }
+ if score < QoO {
+ QoO = score
+ }
+ }
+ return QoO
+}
+
+func (qa *SimpleQualityAttenuation) GetGamingQoO() float64 {
+ qualReq := qualityRequirement{}
+ qualReq.latencyRequirements = []percentileLatencyPair{}
+ qualReq.latencyRequirements = append(qualReq.latencyRequirements, percentileLatencyPair{percentile: 50.0, perfectLatency: 0.030, uselessLatency: 0.150})
+ qualReq.latencyRequirements = append(qualReq.latencyRequirements, percentileLatencyPair{percentile: 95.0, perfectLatency: 0.065, uselessLatency: 0.200})
+ qualReq.latencyRequirements = append(qualReq.latencyRequirements, percentileLatencyPair{percentile: 99.0, perfectLatency: 0.100, uselessLatency: 0.250})
+ return qa.QoO(qualReq)
+}