github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/rpm/calculations.go (about)

     1  /*
     2   * This file is part of Go Responsiveness.
     3   *
     4   * Go Responsiveness is free software: you can redistribute it and/or modify it under
     5   * the terms of the GNU General Public License as published by the Free Software Foundation,
     6   * either version 2 of the License, or (at your option) any later version.
     7   * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
     8   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     9   * PARTICULAR PURPOSE. See the GNU General Public License for more details.
    10   *
    11   * You should have received a copy of the GNU General Public License along
    12   * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
    13   */
    14  
    15  package rpm
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/network-quality/goresponsiveness/series"
    21  	"github.com/network-quality/goresponsiveness/utilities"
    22  )
    23  
    24  type Rpm[Data utilities.Number] struct {
    25  	SelfRttsTotal       int
    26  	ForeignRttsTotal    int
    27  	SelfRttsTrimmed     int
    28  	ForeignRttsTrimmed  int
    29  	SelfProbeRttPN      Data
    30  	ForeignProbeRttPN   Data
    31  	SelfProbeRttMean    float64
    32  	ForeignProbeRttMean float64
    33  	PNRpm               float64
    34  	MeanRpm             float64
    35  }
    36  
    37  func CalculateRpm[Data utilities.Number, Bucket utilities.Number](
    38  	selfRtts series.WindowSeries[Data, Bucket], aggregatedForeignRtts series.WindowSeries[Data, Bucket], trimming uint, percentile uint,
    39  ) *Rpm[Data] {
    40  	// There may be more than one round trip accumulated together. If that is the case,
    41  	// we will blow them apart in to three separate measurements and each one will just
    42  	// be 1 / 3.
    43  	foreignRtts := series.NewWindowSeries[Data, int](series.Forever, 0)
    44  	foreignBuckets := 0
    45  	for _, v := range aggregatedForeignRtts.GetValues() {
    46  		if utilities.IsSome(v) {
    47  			v := utilities.GetSome(v)
    48  			foreignRtts.Reserve(foreignBuckets)
    49  			foreignRtts.Reserve(foreignBuckets + 1)
    50  			foreignRtts.Reserve(foreignBuckets + 2)
    51  			foreignRtts.Fill(foreignBuckets, v/3)
    52  			foreignRtts.Fill(foreignBuckets+1, v/3)
    53  			foreignRtts.Fill(foreignBuckets+2, v/3)
    54  			foreignBuckets += 3
    55  		}
    56  	}
    57  
    58  	boundedSelfRtts := selfRtts.ExtractBoundedSeries()
    59  
    60  	// First, let's do a double-sided trim of the top/bottom 10% of our measurements.
    61  	selfRttsTotalCount, _ := boundedSelfRtts.Count()
    62  	foreignRttsTotalCount, _ := foreignRtts.Count()
    63  
    64  	_, selfProbeRoundTripTimeMean, selfRttsTrimmed :=
    65  		series.TrimmedMean(boundedSelfRtts, int(trimming))
    66  	_, foreignProbeRoundTripTimeMean, foreignRttsTrimmed :=
    67  		series.TrimmedMean(foreignRtts, int(trimming))
    68  
    69  	selfRttsTrimmedCount := len(selfRttsTrimmed)
    70  	foreignRttsTrimmedCount := len(foreignRttsTrimmed)
    71  
    72  	// Second, let's do the P90 calculations.
    73  	_, selfProbeRoundTripTimePN := series.Percentile(boundedSelfRtts, percentile)
    74  	_, foreignProbeRoundTripTimePN := series.Percentile(foreignRtts, percentile)
    75  
    76  	// Note: The specification indicates that we want to calculate the foreign probes as such:
    77  	// 1/3*tcp_foreign + 1/3*tls_foreign + 1/3*http_foreign
    78  	// where tcp_foreign, tls_foreign, http_foreign are the P90 RTTs for the connection
    79  	// of the tcp, tls and http connections, respectively. However, we cannot break out
    80  	// the individual RTTs so we assume that they are roughly equal.
    81  
    82  	// This is 60 because we measure in seconds not ms
    83  	pnRpm := 60.0 / (float64(selfProbeRoundTripTimePN+foreignProbeRoundTripTimePN) / 2.0)
    84  	meanRpm := 60.0 / (float64(selfProbeRoundTripTimeMean+foreignProbeRoundTripTimeMean) / 2.0)
    85  
    86  	return &Rpm[Data]{
    87  		SelfRttsTotal: selfRttsTotalCount, ForeignRttsTotal: foreignRttsTotalCount,
    88  		SelfRttsTrimmed: selfRttsTrimmedCount, ForeignRttsTrimmed: foreignRttsTrimmedCount,
    89  		SelfProbeRttPN: selfProbeRoundTripTimePN, ForeignProbeRttPN: foreignProbeRoundTripTimePN,
    90  		SelfProbeRttMean: selfProbeRoundTripTimeMean, ForeignProbeRttMean: foreignProbeRoundTripTimeMean,
    91  		PNRpm: pnRpm, MeanRpm: meanRpm,
    92  	}
    93  }
    94  
    95  func (rpm *Rpm[Data]) ToString() string {
    96  	return fmt.Sprintf(
    97  		`Total Self Probes:            %d
    98  Total Foreign Probes:         %d
    99  Trimmed Self Probes Count:    %d
   100  Trimmed Foreign Probes Count: %d
   101  P90 Self RTT:                 %v
   102  P90 Foreign RTT:              %v
   103  Trimmed Mean Self RTT:        %f
   104  Trimmed Mean Foreign RTT:     %f
   105  `,
   106  		rpm.SelfRttsTotal,
   107  		rpm.ForeignRttsTotal,
   108  		rpm.SelfRttsTrimmed,
   109  		rpm.ForeignRttsTrimmed,
   110  		rpm.SelfProbeRttPN,
   111  		rpm.ForeignProbeRttPN,
   112  		rpm.SelfProbeRttMean,
   113  		rpm.ForeignProbeRttMean,
   114  	)
   115  }