summaryrefslogtreecommitdiff
path: root/stabilizer
diff options
context:
space:
mode:
Diffstat (limited to 'stabilizer')
-rw-r--r--stabilizer/algorithm.go130
-rw-r--r--stabilizer/rev3.go199
2 files changed, 130 insertions, 199 deletions
diff --git a/stabilizer/algorithm.go b/stabilizer/algorithm.go
new file mode 100644
index 0000000..45c34d9
--- /dev/null
+++ b/stabilizer/algorithm.go
@@ -0,0 +1,130 @@
+/*
+ * This file is part of Go Responsiveness.
+ *
+ * Go Responsiveness is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software Foundation,
+ * either version 2 of the License, or (at your option) any later version.
+ * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package stabilizer
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/network-quality/goresponsiveness/debug"
+ "github.com/network-quality/goresponsiveness/ms"
+ "golang.org/x/exp/constraints"
+)
+
+type MeasurementStablizer[T constraints.Float | constraints.Integer] struct {
+ instantaneousses ms.MathematicalSeries[T]
+ aggregates ms.MathematicalSeries[float64]
+ stabilityStandardDeviation float64
+ trimmingLevel uint
+ m sync.Mutex
+ dbgLevel debug.DebugLevel
+ dbgConfig *debug.DebugWithPrefix
+ units string
+}
+
+// Stabilizer parameters:
+// 1. MAD: An all-purpose value that determines the hysteresis of various calculations
+// that will affect saturation (of either throughput or responsiveness).
+// 2: SDT: The standard deviation cutoff used to determine stability among the K preceding
+// moving averages of a measurement.
+// 3: TMP: The percentage by which to trim the values before calculating the standard deviation
+// to determine whether the value is within acceptable range for stability (SDT).
+
+// Stabilizer Algorithm:
+// Throughput stabilization is achieved when the standard deviation of the MAD number of the most
+// recent moving averages of instantaneous measurements is within an upper bound.
+//
+// Yes, that *is* a little confusing:
+// The user will deliver us a steady diet of measurements of the number of bytes transmitted during the immediately
+// previous interval. We will keep the MAD most recent of those measurements. Every time that we get a new
+// measurement, we will recalculate the moving average of the MAD most instantaneous measurements. We will call that
+// the moving average aggregate throughput at interval p. We keep the MAD most recent of those values.
+// If the calculated standard deviation of *those* values is less than SDT, we declare
+// stability.
+
+func NewStabilizer[T constraints.Float | constraints.Integer](
+ mad uint,
+ sdt float64,
+ trimmingLevel uint,
+ units string,
+ debugLevel debug.DebugLevel,
+ debug *debug.DebugWithPrefix,
+) MeasurementStablizer[T] {
+ return MeasurementStablizer[T]{
+ instantaneousses: ms.NewCappedMathematicalSeries[T](mad),
+ aggregates: ms.NewCappedMathematicalSeries[float64](mad),
+ stabilityStandardDeviation: sdt,
+ trimmingLevel: trimmingLevel,
+ units: units,
+ dbgConfig: debug,
+ dbgLevel: debugLevel,
+ }
+}
+
+func (r3 *MeasurementStablizer[T]) AddMeasurement(measurement T) {
+ r3.m.Lock()
+ defer r3.m.Unlock()
+ // Add this instantaneous measurement to the mix of the MAD previous instantaneous measurements.
+ r3.instantaneousses.AddElement(measurement)
+ // Calculate the moving average of the MAD previous instantaneous measurements (what the
+ // algorithm calls moving average aggregate throughput at interval p) and add it to
+ // the mix of MAD previous moving averages.
+ r3.aggregates.AddElement(r3.instantaneousses.CalculateAverage())
+
+ if debug.IsDebug(r3.dbgLevel) {
+ fmt.Printf(
+ "%s: MA: %f Mbps (previous %d intervals).\n",
+ r3.dbgConfig.String(),
+ r3.aggregates.CalculateAverage(),
+ r3.aggregates.Len(),
+ )
+ }
+}
+
+func (r3 *MeasurementStablizer[T]) IsStable() bool {
+ // There are MAD number of measurements of the _moving average aggregate throughput
+ // at interval p_ in movingAverages.
+ isvalid, stddev := r3.aggregates.StandardDeviation()
+
+ if !isvalid {
+ // If there are not enough values in the series to be able to calculate a
+ // standard deviation, then we know that we are not yet stable. Vamoose.
+ return false
+ }
+
+ // Stability is determined by whether or not the standard deviation of the values
+ // is within some percentage of the average.
+ stabilityCutoff := r3.aggregates.CalculateAverage() * (r3.stabilityStandardDeviation / 100.0)
+ isStable := stddev <= stabilityCutoff
+
+ if debug.IsDebug(r3.dbgLevel) {
+ fmt.Printf(
+ "%s: Is Stable? %v; Standard Deviation: %f %s; Is Normally Distributed? %v; Standard Deviation Cutoff: %v %s).\n",
+ r3.dbgConfig.String(),
+ isStable,
+ stddev,
+ r3.units,
+ r3.aggregates.IsNormallyDistributed(),
+ stabilityCutoff,
+ r3.units,
+ )
+ fmt.Printf("%s: Values: ", r3.dbgConfig.String())
+ for _, v := range r3.aggregates.Values() {
+ fmt.Printf("%v, ", v)
+ }
+ fmt.Printf("\n")
+ }
+ return isStable
+}
diff --git a/stabilizer/rev3.go b/stabilizer/rev3.go
deleted file mode 100644
index 4c24f54..0000000
--- a/stabilizer/rev3.go
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * This file is part of Go Responsiveness.
- *
- * Go Responsiveness is free software: you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software Foundation,
- * either version 2 of the License, or (at your option) any later version.
- * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package stabilizer
-
-import (
- "fmt"
- "sync"
-
- "github.com/network-quality/goresponsiveness/debug"
- "github.com/network-quality/goresponsiveness/ms"
- "github.com/network-quality/goresponsiveness/probe"
- "github.com/network-quality/goresponsiveness/rpm"
- "github.com/network-quality/goresponsiveness/utilities"
-)
-
-type DataPointStabilizer struct {
- instantaneousMeasurements ms.MathematicalSeries[float64]
- movingAverages ms.MathematicalSeries[float64]
- stabilityStandardDeviation float64
- m sync.Mutex
- dbgLevel debug.DebugLevel
- dbgConfig *debug.DebugWithPrefix
-}
-
-type ProbeStabilizer DataPointStabilizer
-type ThroughputStabilizer DataPointStabilizer
-
-// Stabilizer parameters:
-// 1. I: The number of previous instantaneous measurements to consider when generating
-// the so-called instantaneous moving averages.
-// 2. K: The number of instantaneous moving averages to consider when determining stability.
-// 3: S: The standard deviation cutoff used to determine stability among the K preceding
-// moving averages of a measurement.
-
-// Rev3 Stabilizer Algorithm:
-// Stabilization is achieved when the standard deviation of a given number of the most recent moving averages of
-// instantaneous measurements is within an upper bound.
-//
-// Yes, that *is* a little confusing:
-// The user will deliver us a steady diet of so-called instantaneous measurements. We will keep the I most recent
-// of those measurements. Every time that we get a new instantaneous measurement, we will recalculate the moving
-// average of the I most instantaneous measurements. We will call that an instantaneous moving average. We keep the K
-// most recent instantaneous moving averages. Every time that we calculate a new instantaneous moving average, we will
-// calculate the standard deviation of those values. If the calculated standard deviation is less than S, we declare
-// stability.
-
-func NewProbeStabilizer(
- i uint64,
- k uint64,
- s float64,
- debugLevel debug.DebugLevel,
- debug *debug.DebugWithPrefix,
-) ProbeStabilizer {
- return ProbeStabilizer{instantaneousMeasurements: ms.NewCappedMathematicalSeries[float64](i),
- movingAverages: ms.NewCappedMathematicalSeries[float64](k),
- stabilityStandardDeviation: s,
- dbgConfig: debug,
- dbgLevel: debugLevel}
-}
-
-func (r3 *ProbeStabilizer) AddMeasurement(measurement probe.ProbeDataPoint) {
- r3.m.Lock()
- defer r3.m.Unlock()
-
- // There may be more than one round trip accumulated together. If that is the case,
- // we will blow them apart in to three separate measurements and each one will just
- // be 1 / measurement.RoundTripCount of the total length.
- for range utilities.Iota(0, int(measurement.RoundTripCount)) {
- // Add this instantaneous measurement to the mix of the I previous instantaneous measurements.
- r3.instantaneousMeasurements.AddElement(
- measurement.Duration.Seconds() / float64(measurement.RoundTripCount),
- )
- }
- // Calculate the moving average of the I previous instantaneous measurements and add it to
- // the mix of K previous moving averages.
- r3.movingAverages.AddElement(r3.instantaneousMeasurements.CalculateAverage())
-
- if debug.IsDebug(r3.dbgLevel) {
- fmt.Printf(
- "%s: MA: %f ns (previous %d intervals).\n",
- r3.dbgConfig.String(),
- r3.movingAverages.CalculateAverage(),
- r3.movingAverages.Len(),
- )
- }
-}
-
-func (r3 *ProbeStabilizer) IsStable() bool {
- // calculate whether the standard deviation of the K previous moving averages falls below S.
- isvalid, stddev := r3.movingAverages.StandardDeviation()
-
- if !isvalid {
- // If there are not enough values in the series to be able to calculate a
- // standard deviation, then we know that we are not yet stable. Vamoose.
- return false
- }
-
- // Stability is determined by whether or not the standard deviation of the values
- // is within some percentage of the average.
- stabilityCutoff := r3.movingAverages.CalculateAverage() * (r3.stabilityStandardDeviation / 100.0)
- isStable := stddev <= stabilityCutoff
-
- if debug.IsDebug(r3.dbgLevel) {
- fmt.Printf(
- "%s: Is Stable? %v; Standard Deviation: %f s; Is Normally Distributed? %v; Standard Deviation Cutoff: %v s).\n",
- r3.dbgConfig.String(),
- isStable,
- stddev,
- r3.movingAverages.IsNormallyDistributed(),
- stabilityCutoff,
- )
- fmt.Printf("%s: Values: ", r3.dbgConfig.String())
- for _, v := range r3.movingAverages.Values() {
- fmt.Printf("%v, ", v)
- }
- fmt.Printf("\n")
- }
-
- return isStable
-}
-
-func NewThroughputStabilizer(
- i uint64,
- k uint64,
- s float64,
- debugLevel debug.DebugLevel,
- debug *debug.DebugWithPrefix,
-) ThroughputStabilizer {
- return ThroughputStabilizer{
- instantaneousMeasurements: ms.NewCappedMathematicalSeries[float64](i),
- movingAverages: ms.NewCappedMathematicalSeries[float64](k),
- stabilityStandardDeviation: s,
- dbgConfig: debug,
- dbgLevel: debugLevel,
- }
-}
-
-func (r3 *ThroughputStabilizer) AddMeasurement(measurement rpm.ThroughputDataPoint) {
- r3.m.Lock()
- defer r3.m.Unlock()
- // Add this instantaneous measurement to the mix of the I previous instantaneous measurements.
- r3.instantaneousMeasurements.AddElement(utilities.ToMbps(measurement.Throughput))
- // Calculate the moving average of the I previous instantaneous measurements and add it to
- // the mix of K previous moving averages.
- r3.movingAverages.AddElement(r3.instantaneousMeasurements.CalculateAverage())
-
- if debug.IsDebug(r3.dbgLevel) {
- fmt.Printf(
- "%s: MA: %f Mbps (previous %d intervals).\n",
- r3.dbgConfig.String(),
- r3.movingAverages.CalculateAverage(),
- r3.movingAverages.Len(),
- )
- }
-}
-
-func (r3 *ThroughputStabilizer) IsStable() bool {
- isvalid, stddev := r3.movingAverages.StandardDeviation()
-
- if !isvalid {
- // If there are not enough values in the series to be able to calculate a
- // standard deviation, then we know that we are not yet stable. Vamoose.
- return false
- }
-
- // Stability is determined by whether or not the standard deviation of the values
- // is within some percentage of the average.
- stabilityCutoff := r3.movingAverages.CalculateAverage() * (r3.stabilityStandardDeviation / 100.0)
- isStable := stddev <= stabilityCutoff
-
- if debug.IsDebug(r3.dbgLevel) {
- fmt.Printf(
- "%s: Is Stable? %v; Standard Deviation: %f Mbps; Is Normally Distributed? %v; Standard Deviation Cutoff: %v Mbps).\n",
- r3.dbgConfig.String(),
- isStable,
- stddev,
- r3.movingAverages.IsNormallyDistributed(),
- stabilityCutoff,
- )
- fmt.Printf("%s: Values: ", r3.dbgConfig.String())
- for _, v := range r3.movingAverages.Values() {
- fmt.Printf("%v, ", v)
- }
- fmt.Printf("\n")
- }
- return isStable
-}