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  }