github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/time/calibrated_clock.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 provides a calibrated clock synchronized to a system reference
    16  // clock.
    17  package time
    18  
    19  import (
    20  	"time"
    21  
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/metric"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    26  )
    27  
    28  // CalibratedClock implements a clock that tracks a reference clock.
    29  //
    30  // Users should call Update at regular intervals of around approxUpdateInterval
    31  // to ensure that the clock does not drift significantly from the reference
    32  // clock.
    33  type CalibratedClock struct {
    34  	// mu protects the fields below.
    35  	// TODO(mpratt): consider a sequence counter for read locking.
    36  	mu sync.RWMutex
    37  
    38  	// ref sample the reference clock that this clock is calibrated
    39  	// against.
    40  	ref *sampler
    41  
    42  	// ready indicates that the fields below are ready for use calculating
    43  	// time.
    44  	ready bool
    45  
    46  	// params are the current timekeeping parameters.
    47  	params Parameters
    48  
    49  	// errorNS is the estimated clock error in nanoseconds.
    50  	errorNS ReferenceNS
    51  }
    52  
    53  // NewCalibratedClock creates a CalibratedClock that tracks the given ClockID.
    54  func NewCalibratedClock(c ClockID) *CalibratedClock {
    55  	return &CalibratedClock{
    56  		ref: newSampler(c),
    57  	}
    58  }
    59  
    60  // Debugf logs at debug level.
    61  func (c *CalibratedClock) Debugf(format string, v ...any) {
    62  	if log.IsLogging(log.Debug) {
    63  		args := []any{c.ref.clockID}
    64  		args = append(args, v...)
    65  		log.Debugf("CalibratedClock(%v): "+format, args...)
    66  	}
    67  }
    68  
    69  // Infof logs at debug level.
    70  func (c *CalibratedClock) Infof(format string, v ...any) {
    71  	if log.IsLogging(log.Info) {
    72  		args := []any{c.ref.clockID}
    73  		args = append(args, v...)
    74  		log.Infof("CalibratedClock(%v): "+format, args...)
    75  	}
    76  }
    77  
    78  // Warningf logs at debug level.
    79  func (c *CalibratedClock) Warningf(format string, v ...any) {
    80  	if log.IsLogging(log.Warning) {
    81  		args := []any{c.ref.clockID}
    82  		args = append(args, v...)
    83  		log.Warningf("CalibratedClock(%v): "+format, args...)
    84  	}
    85  }
    86  
    87  // reset forces the clock to restart the calibration process, logging the
    88  // passed message.
    89  func (c *CalibratedClock) reset(str string, v ...any) {
    90  	c.mu.Lock()
    91  	defer c.mu.Unlock()
    92  	c.resetLocked(str, v...)
    93  }
    94  
    95  // resetLocked is equivalent to reset with c.mu already held for writing.
    96  func (c *CalibratedClock) resetLocked(str string, v ...any) {
    97  	c.Warningf(str+" Resetting clock; time may jump.", v...)
    98  	c.ready = false
    99  	c.ref.Reset()
   100  	metric.WeirdnessMetric.Increment(&metric.WeirdnessTypeTimeFallback)
   101  }
   102  
   103  // updateParams updates the timekeeping parameters based on the passed
   104  // parameters.
   105  //
   106  // actual is the actual estimated timekeeping parameters. The stored parameters
   107  // may need to be adjusted slightly from these values to compensate for error.
   108  //
   109  // Preconditions: c.mu must be held for writing.
   110  func (c *CalibratedClock) updateParams(actual Parameters) {
   111  	if !c.ready {
   112  		// At initial calibration there is nothing to correct.
   113  		c.params = actual
   114  		c.ready = true
   115  
   116  		c.Infof("ready")
   117  
   118  		return
   119  	}
   120  
   121  	// Otherwise, adjust the params to correct for errors.
   122  	newParams, errorNS, err := errorAdjust(c.params, actual, actual.BaseCycles)
   123  	if err != nil {
   124  		// Something is very wrong. Reset and try again from the
   125  		// beginning.
   126  		c.resetLocked("Unable to update params: %v.", err)
   127  		return
   128  	}
   129  	logErrorAdjustment(c.ref.clockID, errorNS, c.params, newParams)
   130  
   131  	if errorNS.Magnitude() >= MaxClockError {
   132  		// We should never get such extreme error, something is very
   133  		// wrong. Reset everything and start again.
   134  		//
   135  		// N.B. logErrorAdjustment will have already logged the error
   136  		// at warning level.
   137  		//
   138  		// TODO(mpratt): We could allow Realtime clock jumps here.
   139  		c.resetLocked("Extreme clock error.")
   140  		return
   141  	}
   142  
   143  	c.params = newParams
   144  	c.errorNS = errorNS
   145  }
   146  
   147  // Update runs the update step of the clock, updating its synchronization with
   148  // the reference clock.
   149  //
   150  // Update returns timekeeping and true with the new timekeeping parameters if
   151  // the clock is calibrated. Update should be called regularly to prevent the
   152  // clock from getting significantly out of sync from the reference clock.
   153  //
   154  // The returned timekeeping parameters are invalidated on the next call to
   155  // Update.
   156  func (c *CalibratedClock) Update() (Parameters, bool) {
   157  	c.mu.Lock()
   158  	defer c.mu.Unlock()
   159  
   160  	if err := c.ref.Sample(); err != nil {
   161  		c.resetLocked("Unable to update calibrated clock: %v.", err)
   162  		return Parameters{}, false
   163  	}
   164  
   165  	oldest, newest, ok := c.ref.Range()
   166  	if !ok {
   167  		// Not ready yet.
   168  		return Parameters{}, false
   169  	}
   170  
   171  	minCount := uint64(newest.before - oldest.after)
   172  	maxCount := uint64(newest.after - oldest.before)
   173  	refInterval := uint64(newest.ref - oldest.ref)
   174  
   175  	// freq hz = count / (interval ns) * (nsPerS ns) / (1 s)
   176  	nsPerS := uint64(time.Second.Nanoseconds())
   177  
   178  	minHz, ok := muldiv64(minCount, nsPerS, refInterval)
   179  	if !ok {
   180  		c.resetLocked("Unable to update calibrated clock: (%v - %v) * %v / %v overflows.", newest.before, oldest.after, nsPerS, refInterval)
   181  		return Parameters{}, false
   182  	}
   183  
   184  	maxHz, ok := muldiv64(maxCount, nsPerS, refInterval)
   185  	if !ok {
   186  		c.resetLocked("Unable to update calibrated clock: (%v - %v) * %v / %v overflows.", newest.after, oldest.before, nsPerS, refInterval)
   187  		return Parameters{}, false
   188  	}
   189  
   190  	c.updateParams(Parameters{
   191  		Frequency:  (minHz + maxHz) / 2,
   192  		BaseRef:    newest.ref,
   193  		BaseCycles: newest.after,
   194  	})
   195  
   196  	return c.params, true
   197  }
   198  
   199  // GetTime returns the current time based on the clock calibration.
   200  func (c *CalibratedClock) GetTime() (int64, error) {
   201  	c.mu.RLock()
   202  
   203  	if !c.ready {
   204  		// Fallback to a syscall.
   205  		now, err := c.ref.Syscall()
   206  		c.mu.RUnlock()
   207  		return int64(now), err
   208  	}
   209  
   210  	now := c.ref.Cycles()
   211  	v, ok := c.params.ComputeTime(now)
   212  	if !ok {
   213  		// Something is seriously wrong with the clock. Try
   214  		// again with syscalls.
   215  		c.resetLocked("Time computation overflowed. params = %+v, now = %v.", c.params, now)
   216  		now, err := c.ref.Syscall()
   217  		c.mu.RUnlock()
   218  		return int64(now), err
   219  	}
   220  
   221  	c.mu.RUnlock()
   222  	return v, nil
   223  }
   224  
   225  // CalibratedClocks contains calibrated monotonic and realtime clocks.
   226  //
   227  // TODO(mpratt): We know that Linux runs the monotonic and realtime clocks at
   228  // the same rate, so rather than tracking both individually, we could do one
   229  // calibration for both clocks.
   230  type CalibratedClocks struct {
   231  	// monotonic is the clock tracking the system monotonic clock.
   232  	monotonic *CalibratedClock
   233  
   234  	// realtime is the realtime equivalent of monotonic.
   235  	realtime *CalibratedClock
   236  }
   237  
   238  // NewCalibratedClocks creates a CalibratedClocks.
   239  func NewCalibratedClocks() *CalibratedClocks {
   240  	return &CalibratedClocks{
   241  		monotonic: NewCalibratedClock(Monotonic),
   242  		realtime:  NewCalibratedClock(Realtime),
   243  	}
   244  }
   245  
   246  // Update implements Clocks.Update.
   247  func (c *CalibratedClocks) Update() (Parameters, bool, Parameters, bool) {
   248  	monotonicParams, monotonicOk := c.monotonic.Update()
   249  	realtimeParams, realtimeOk := c.realtime.Update()
   250  
   251  	return monotonicParams, monotonicOk, realtimeParams, realtimeOk
   252  }
   253  
   254  // GetTime implements Clocks.GetTime.
   255  func (c *CalibratedClocks) GetTime(id ClockID) (int64, error) {
   256  	switch id {
   257  	case Monotonic:
   258  		return c.monotonic.GetTime()
   259  	case Realtime:
   260  		return c.realtime.GetTime()
   261  	default:
   262  		return 0, linuxerr.EINVAL
   263  	}
   264  }