gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/time/parameters.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package time 16 17 import ( 18 "fmt" 19 "time" 20 21 "gvisor.dev/gvisor/pkg/log" 22 ) 23 24 const ( 25 // ApproxUpdateInterval is the approximate interval that parameters 26 // should be updated at. 27 // 28 // Error correction assumes that the next update will occur after this 29 // much time. 30 // 31 // If an update occurs before ApproxUpdateInterval passes, it has no 32 // adverse effect on error correction behavior. 33 // 34 // If an update occurs after ApproxUpdateInterval passes, the clock 35 // will overshoot its error correction target and begin accumulating 36 // error in the other direction. 37 // 38 // If updates occur after more than 2*ApproxUpdateInterval passes, the 39 // clock becomes unstable, accumulating more error than it had 40 // originally. Repeated updates after more than 2*ApproxUpdateInterval 41 // will cause unbounded increases in error. 42 // 43 // These statements assume that the host clock does not change. Actual 44 // error will depend upon host clock changes. 45 // 46 // TODO(b/68779214): make error correction more robust to delayed 47 // updates. 48 ApproxUpdateInterval = 1 * time.Second 49 50 // MaxClockError is the maximum amount of error that the clocks will 51 // try to correct. 52 // 53 // This limit: 54 // 55 // * Puts a limit on cases of otherwise unbounded increases in error. 56 // 57 // * Avoids unreasonably large frequency adjustments required to 58 // correct large errors over a single update interval. 59 MaxClockError = ReferenceNS(ApproxUpdateInterval) / 4 60 ) 61 62 // Parameters are the timekeeping parameters needed to compute the current 63 // time. 64 type Parameters struct { 65 // BaseCycles was the TSC counter value when the time was BaseRef. 66 BaseCycles TSCValue 67 68 // BaseRef is the reference clock time in nanoseconds corresponding to 69 // BaseCycles. 70 BaseRef ReferenceNS 71 72 // Frequency is the frequency of the cycle clock in Hertz. 73 Frequency uint64 74 } 75 76 // muldiv64 multiplies two 64-bit numbers, then divides the result by another 77 // 64-bit number. 78 // 79 // It requires that the result fit in 64 bits, but doesn't require that 80 // intermediate values do; in particular, the result of the multiplication may 81 // require 128 bits. 82 // 83 // It returns !ok if divisor is zero or the result does not fit in 64 bits. 84 func muldiv64(value, multiplier, divisor uint64) (uint64, bool) 85 86 // ComputeTime calculates the current time from a "now" TSC value. 87 // 88 // time = ref + (now - base) / f 89 func (p Parameters) ComputeTime(nowCycles TSCValue) (int64, bool) { 90 diffCycles := nowCycles - p.BaseCycles 91 if diffCycles < 0 { 92 log.Warningf("now cycles %v < base cycles %v", nowCycles, p.BaseCycles) 93 diffCycles = 0 94 } 95 96 // Overflow "won't ever happen". If diffCycles is the max value 97 // (2^63 - 1), then to overflow, 98 // 99 // frequency <= ((2^63 - 1) * 10^9) / 2^64 = 500Mhz 100 // 101 // A TSC running at 2GHz takes 201 years to reach 2^63-1. 805 years at 102 // 500MHz. 103 diffNS, ok := muldiv64(uint64(diffCycles), uint64(time.Second.Nanoseconds()), p.Frequency) 104 return int64(uint64(p.BaseRef) + diffNS), ok 105 } 106 107 // errorAdjust returns a new Parameters struct "adjusted" that satisfies: 108 // 109 // 1. adjusted.ComputeTime(now) = prevParams.ComputeTime(now) 110 // - i.e., the current time does not jump. 111 // 112 // 2. adjusted.ComputeTime(TSC at next update) = newParams.ComputeTime(TSC at next update) 113 // - i.e., Any error between prevParams and newParams will be corrected over 114 // the course of the next update period. 115 // 116 // errorAdjust also returns the current clock error. 117 // 118 // Preconditions: 119 // - newParams.BaseCycles >= prevParams.BaseCycles; i.e., TSC must not go 120 // backwards. 121 // - newParams.BaseCycles <= now; i.e., the new parameters be computed at or 122 // before now. 123 func errorAdjust(prevParams Parameters, newParams Parameters, now TSCValue) (Parameters, ReferenceNS, error) { 124 if newParams.BaseCycles < prevParams.BaseCycles { 125 // Oh dear! Something is very wrong. 126 return Parameters{}, 0, fmt.Errorf("TSC went backwards in updated clock params: %v < %v", newParams.BaseCycles, prevParams.BaseCycles) 127 } 128 if newParams.BaseCycles > now { 129 return Parameters{}, 0, fmt.Errorf("parameters contain base cycles later than now: %v > %v", newParams.BaseCycles, now) 130 } 131 132 intervalNS := int64(ApproxUpdateInterval.Nanoseconds()) 133 nsPerSec := uint64(time.Second.Nanoseconds()) 134 135 // Current time as computed by prevParams. 136 oldNowNS, ok := prevParams.ComputeTime(now) 137 if !ok { 138 return Parameters{}, 0, fmt.Errorf("old now time computation overflowed. params = %+v, now = %v", prevParams, now) 139 } 140 141 // We expect the update ticker to run based on this clock (i.e., it has 142 // been using prevParams and will use the returned adjusted 143 // parameters). Hence it will decide to fire intervalNS from the 144 // current (oldNowNS) "now". 145 nextNS := oldNowNS + intervalNS 146 147 if nextNS <= int64(newParams.BaseRef) { 148 // The next update time already passed before the new 149 // parameters were created! We definitely can't correct the 150 // error by then. 151 return Parameters{}, 0, fmt.Errorf("unable to correct error in single period. oldNowNS = %v, nextNS = %v, p = %v", oldNowNS, nextNS, newParams) 152 } 153 154 // For what TSC value next will newParams.ComputeTime(next) = nextNS? 155 // 156 // Solve ComputeTime for next: 157 // 158 // next = newParams.Frequency * (nextNS - newParams.BaseRef) + newParams.BaseCycles 159 c, ok := muldiv64(newParams.Frequency, uint64(nextNS-int64(newParams.BaseRef)), nsPerSec) 160 if !ok { 161 return Parameters{}, 0, fmt.Errorf("%v * (%v - %v) / %v overflows", newParams.Frequency, nextNS, newParams.BaseRef, nsPerSec) 162 } 163 164 cycles := TSCValue(c) 165 next := cycles + newParams.BaseCycles 166 167 if next <= now { 168 // The next update time already passed now with the new 169 // parameters! We can't correct the error in a single period. 170 return Parameters{}, 0, fmt.Errorf("unable to correct error in single period. oldNowNS = %v, nextNS = %v, now = %v, next = %v", oldNowNS, nextNS, now, next) 171 } 172 173 // We want to solve for parameters that satisfy: 174 // 175 // adjusted.ComputeTime(now) = oldNowNS 176 // 177 // adjusted.ComputeTime(next) = nextNS 178 // 179 // i.e., the current time does not change, but by the time we reach 180 // next we reach the same time as newParams. 181 182 // We choose to keep BaseCycles fixed. 183 adjusted := Parameters{ 184 BaseCycles: newParams.BaseCycles, 185 } 186 187 // We want a slope such that time goes from oldNowNS to nextNS when 188 // we reach next. 189 // 190 // In other words, cycles should increase by next - now in the next 191 // interval. 192 193 cycles = next - now 194 ns := intervalNS 195 196 // adjusted.Frequency = cycles / ns 197 adjusted.Frequency, ok = muldiv64(uint64(cycles), nsPerSec, uint64(ns)) 198 if !ok { 199 return Parameters{}, 0, fmt.Errorf("(%v - %v) * %v / %v overflows", next, now, nsPerSec, ns) 200 } 201 202 // Now choose a base reference such that the current time remains the 203 // same. Note that this is just ComputeTime, solving for BaseRef: 204 // 205 // oldNowNS = BaseRef + (now - BaseCycles) / Frequency 206 // BaseRef = oldNowNS - (now - BaseCycles) / Frequency 207 diffNS, ok := muldiv64(uint64(now-adjusted.BaseCycles), nsPerSec, adjusted.Frequency) 208 if !ok { 209 return Parameters{}, 0, fmt.Errorf("(%v - %v) * %v / %v overflows", now, adjusted.BaseCycles, nsPerSec, adjusted.Frequency) 210 } 211 212 adjusted.BaseRef = ReferenceNS(oldNowNS - int64(diffNS)) 213 214 // The error is the difference between the current time and what the 215 // new parameters say the current time should be. 216 newNowNS, ok := newParams.ComputeTime(now) 217 if !ok { 218 return Parameters{}, 0, fmt.Errorf("new now time computation overflowed. params = %+v, now = %v", newParams, now) 219 } 220 221 errorNS := ReferenceNS(oldNowNS - newNowNS) 222 223 return adjusted, errorNS, nil 224 } 225 226 // logErrorAdjustment logs the clock error and associated error correction 227 // frequency adjustment. 228 // 229 // The log level is determined by the error severity. 230 func logErrorAdjustment(clock ClockID, errorNS ReferenceNS, orig, adjusted Parameters) { 231 magNS := int64(errorNS.Magnitude()) 232 if magNS <= time.Millisecond.Nanoseconds() { 233 // Don't log small errors. 234 return 235 } 236 237 log.Warningf("Clock(%v): error: %v ns, adjusted frequency from %v Hz to %v Hz", clock, errorNS, orig.Frequency, adjusted.Frequency) 238 }