k8s.io/apiserver@v0.31.1/pkg/util/flowcontrol/fairqueuing/integrator.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fairqueuing 18 19 import ( 20 "math" 21 "sync" 22 "time" 23 24 fcmetrics "k8s.io/apiserver/pkg/util/flowcontrol/metrics" 25 26 "k8s.io/utils/clock" 27 ) 28 29 // Integrator computes the moments of some variable X over time as 30 // read from a particular clock. The integrals start when the 31 // Integrator is created, and ends at the latest operation on the 32 // Integrator. 33 type Integrator interface { 34 fcmetrics.Gauge 35 36 GetResults() IntegratorResults 37 38 // Return the results of integrating to now, and reset integration to start now 39 Reset() IntegratorResults 40 } 41 42 // IntegratorResults holds statistical abstracts of the integration 43 type IntegratorResults struct { 44 Duration float64 //seconds 45 Average float64 //time-weighted 46 Deviation float64 //standard deviation: sqrt(avg((value-avg)^2)) 47 Min, Max float64 48 } 49 50 // Equal tests for semantic equality. 51 // This considers all NaN values to be equal to each other. 52 func (x *IntegratorResults) Equal(y *IntegratorResults) bool { 53 return x == y || x != nil && y != nil && x.Duration == y.Duration && x.Min == y.Min && x.Max == y.Max && (x.Average == y.Average || math.IsNaN(x.Average) && math.IsNaN(y.Average)) && (x.Deviation == y.Deviation || math.IsNaN(x.Deviation) && math.IsNaN(y.Deviation)) 54 } 55 56 type integrator struct { 57 name string 58 clock clock.PassiveClock 59 sync.Mutex 60 lastTime time.Time 61 x float64 62 moments Moments 63 min, max float64 64 } 65 66 // NewNamedIntegrator makes one that uses the given clock and name 67 func NewNamedIntegrator(clock clock.PassiveClock, name string) Integrator { 68 return &integrator{ 69 name: name, 70 clock: clock, 71 lastTime: clock.Now(), 72 } 73 } 74 75 func (igr *integrator) Set(x float64) { 76 igr.Lock() 77 igr.setLocked(x) 78 igr.Unlock() 79 } 80 81 func (igr *integrator) Add(deltaX float64) { 82 igr.Lock() 83 igr.setLocked(igr.x + deltaX) 84 igr.Unlock() 85 } 86 87 func (igr *integrator) Inc() { 88 igr.Add(1) 89 } 90 91 func (igr *integrator) Dec() { 92 igr.Add(-1) 93 } 94 95 func (igr *integrator) SetToCurrentTime() { 96 igr.Set(float64(time.Now().UnixNano())) 97 } 98 99 func (igr *integrator) setLocked(x float64) { 100 igr.updateLocked() 101 igr.x = x 102 if x < igr.min { 103 igr.min = x 104 } 105 if x > igr.max { 106 igr.max = x 107 } 108 } 109 110 func (igr *integrator) updateLocked() { 111 now := igr.clock.Now() 112 dt := now.Sub(igr.lastTime).Seconds() 113 igr.lastTime = now 114 igr.moments = igr.moments.Add(ConstantMoments(dt, igr.x)) 115 } 116 117 func (igr *integrator) GetResults() IntegratorResults { 118 igr.Lock() 119 defer igr.Unlock() 120 return igr.getResultsLocked() 121 } 122 123 func (igr *integrator) Reset() IntegratorResults { 124 igr.Lock() 125 defer igr.Unlock() 126 results := igr.getResultsLocked() 127 igr.moments = Moments{} 128 igr.min = igr.x 129 igr.max = igr.x 130 return results 131 } 132 133 func (igr *integrator) getResultsLocked() (results IntegratorResults) { 134 igr.updateLocked() 135 results.Min, results.Max = igr.min, igr.max 136 results.Duration = igr.moments.ElapsedSeconds 137 results.Average, results.Deviation = igr.moments.AvgAndStdDev() 138 return 139 } 140 141 // Moments are the integrals of the 0, 1, and 2 powers of some 142 // variable X over some range of time. 143 type Moments struct { 144 ElapsedSeconds float64 // integral of dt 145 IntegralX float64 // integral of x dt 146 IntegralXX float64 // integral of x*x dt 147 } 148 149 // ConstantMoments is for a constant X 150 func ConstantMoments(dt, x float64) Moments { 151 return Moments{ 152 ElapsedSeconds: dt, 153 IntegralX: x * dt, 154 IntegralXX: x * x * dt, 155 } 156 } 157 158 // Add combines over two ranges of time 159 func (igr Moments) Add(ogr Moments) Moments { 160 return Moments{ 161 ElapsedSeconds: igr.ElapsedSeconds + ogr.ElapsedSeconds, 162 IntegralX: igr.IntegralX + ogr.IntegralX, 163 IntegralXX: igr.IntegralXX + ogr.IntegralXX, 164 } 165 } 166 167 // Sub finds the difference between a range of time and a subrange 168 func (igr Moments) Sub(ogr Moments) Moments { 169 return Moments{ 170 ElapsedSeconds: igr.ElapsedSeconds - ogr.ElapsedSeconds, 171 IntegralX: igr.IntegralX - ogr.IntegralX, 172 IntegralXX: igr.IntegralXX - ogr.IntegralXX, 173 } 174 } 175 176 // AvgAndStdDev returns the average and standard devation 177 func (igr Moments) AvgAndStdDev() (float64, float64) { 178 if igr.ElapsedSeconds <= 0 { 179 return math.NaN(), math.NaN() 180 } 181 avg := igr.IntegralX / igr.ElapsedSeconds 182 // standard deviation is sqrt( average( (x - xbar)^2 ) ) 183 // = sqrt( Integral( x^2 + xbar^2 -2*x*xbar dt ) / Duration ) 184 // = sqrt( ( Integral( x^2 dt ) + Duration * xbar^2 - 2*xbar*Integral(x dt) ) / Duration) 185 // = sqrt( Integral(x^2 dt)/Duration - xbar^2 ) 186 variance := igr.IntegralXX/igr.ElapsedSeconds - avg*avg 187 if variance >= 0 { 188 return avg, math.Sqrt(variance) 189 } 190 return avg, math.NaN() 191 }