gvisor/pkg/sentry/time/calibrated_clock.go

270 lines
7.9 KiB
Go

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package time provides a calibrated clock synchronized to a system reference
// clock.
package time
import (
"time"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/metric"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
// fallbackMetric tracks failed updates. It is not sync, as it is not critical
// that all occurrences are captured and CalibratedClock may fallback many
// times.
var fallbackMetric = metric.MustCreateNewUint64Metric("/time/fallback", false /* sync */, "Incremented when a clock falls back to system calls due to a failed update")
// CalibratedClock implements a clock that tracks a reference clock.
//
// Users should call Update at regular intervals of around approxUpdateInterval
// to ensure that the clock does not drift significantly from the reference
// clock.
type CalibratedClock struct {
// mu protects the fields below.
// TODO(mpratt): consider a sequence counter for read locking.
mu sync.RWMutex
// ref sample the reference clock that this clock is calibrated
// against.
ref *sampler
// ready indicates that the fields below are ready for use calculating
// time.
ready bool
// params are the current timekeeping parameters.
params Parameters
// errorNS is the estimated clock error in nanoseconds.
errorNS ReferenceNS
}
// NewCalibratedClock creates a CalibratedClock that tracks the given ClockID.
func NewCalibratedClock(c ClockID) *CalibratedClock {
return &CalibratedClock{
ref: newSampler(c),
}
}
// Debugf logs at debug level.
func (c *CalibratedClock) Debugf(format string, v ...interface{}) {
if log.IsLogging(log.Debug) {
args := []interface{}{c.ref.clockID}
args = append(args, v...)
log.Debugf("CalibratedClock(%v): "+format, args...)
}
}
// Infof logs at debug level.
func (c *CalibratedClock) Infof(format string, v ...interface{}) {
if log.IsLogging(log.Info) {
args := []interface{}{c.ref.clockID}
args = append(args, v...)
log.Infof("CalibratedClock(%v): "+format, args...)
}
}
// Warningf logs at debug level.
func (c *CalibratedClock) Warningf(format string, v ...interface{}) {
if log.IsLogging(log.Warning) {
args := []interface{}{c.ref.clockID}
args = append(args, v...)
log.Warningf("CalibratedClock(%v): "+format, args...)
}
}
// reset forces the clock to restart the calibration process, logging the
// passed message.
func (c *CalibratedClock) reset(str string, v ...interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.resetLocked(str, v...)
}
// resetLocked is equivalent to reset with c.mu already held for writing.
func (c *CalibratedClock) resetLocked(str string, v ...interface{}) {
c.Warningf(str+" Resetting clock; time may jump.", v...)
c.ready = false
c.ref.Reset()
fallbackMetric.Increment()
}
// updateParams updates the timekeeping parameters based on the passed
// parameters.
//
// actual is the actual estimated timekeeping parameters. The stored parameters
// may need to be adjusted slightly from these values to compensate for error.
//
// Preconditions: c.mu must be held for writing.
func (c *CalibratedClock) updateParams(actual Parameters) {
if !c.ready {
// At initial calibration there is nothing to correct.
c.params = actual
c.ready = true
c.Infof("ready")
return
}
// Otherwise, adjust the params to correct for errors.
newParams, errorNS, err := errorAdjust(c.params, actual, actual.BaseCycles)
if err != nil {
// Something is very wrong. Reset and try again from the
// beginning.
c.resetLocked("Unable to update params: %v.", err)
return
}
logErrorAdjustment(c.ref.clockID, errorNS, c.params, newParams)
if errorNS.Magnitude() >= MaxClockError {
// We should never get such extreme error, something is very
// wrong. Reset everything and start again.
//
// N.B. logErrorAdjustment will have already logged the error
// at warning level.
//
// TODO(mpratt): We could allow Realtime clock jumps here.
c.resetLocked("Extreme clock error.")
return
}
c.params = newParams
c.errorNS = errorNS
}
// Update runs the update step of the clock, updating its synchronization with
// the reference clock.
//
// Update returns timekeeping and true with the new timekeeping parameters if
// the clock is calibrated. Update should be called regularly to prevent the
// clock from getting significantly out of sync from the reference clock.
//
// The returned timekeeping parameters are invalidated on the next call to
// Update.
func (c *CalibratedClock) Update() (Parameters, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if err := c.ref.Sample(); err != nil {
c.resetLocked("Unable to update calibrated clock: %v.", err)
return Parameters{}, false
}
oldest, newest, ok := c.ref.Range()
if !ok {
// Not ready yet.
return Parameters{}, false
}
minCount := uint64(newest.before - oldest.after)
maxCount := uint64(newest.after - oldest.before)
refInterval := uint64(newest.ref - oldest.ref)
// freq hz = count / (interval ns) * (nsPerS ns) / (1 s)
nsPerS := uint64(time.Second.Nanoseconds())
minHz, ok := muldiv64(minCount, nsPerS, refInterval)
if !ok {
c.resetLocked("Unable to update calibrated clock: (%v - %v) * %v / %v overflows.", newest.before, oldest.after, nsPerS, refInterval)
return Parameters{}, false
}
maxHz, ok := muldiv64(maxCount, nsPerS, refInterval)
if !ok {
c.resetLocked("Unable to update calibrated clock: (%v - %v) * %v / %v overflows.", newest.after, oldest.before, nsPerS, refInterval)
return Parameters{}, false
}
c.updateParams(Parameters{
Frequency: (minHz + maxHz) / 2,
BaseRef: newest.ref,
BaseCycles: newest.after,
})
return c.params, true
}
// GetTime returns the current time based on the clock calibration.
func (c *CalibratedClock) GetTime() (int64, error) {
c.mu.RLock()
if !c.ready {
// Fallback to a syscall.
now, err := c.ref.Syscall()
c.mu.RUnlock()
return int64(now), err
}
now := c.ref.Cycles()
v, ok := c.params.ComputeTime(now)
if !ok {
// Something is seriously wrong with the clock. Try
// again with syscalls.
c.resetLocked("Time computation overflowed. params = %+v, now = %v.", c.params, now)
now, err := c.ref.Syscall()
c.mu.RUnlock()
return int64(now), err
}
c.mu.RUnlock()
return v, nil
}
// CalibratedClocks contains calibrated monotonic and realtime clocks.
//
// TODO(mpratt): We know that Linux runs the monotonic and realtime clocks at
// the same rate, so rather than tracking both individually, we could do one
// calibration for both clocks.
type CalibratedClocks struct {
// monotonic is the clock tracking the system monotonic clock.
monotonic *CalibratedClock
// realtime is the realtime equivalent of monotonic.
realtime *CalibratedClock
}
// NewCalibratedClocks creates a CalibratedClocks.
func NewCalibratedClocks() *CalibratedClocks {
return &CalibratedClocks{
monotonic: NewCalibratedClock(Monotonic),
realtime: NewCalibratedClock(Realtime),
}
}
// Update implements Clocks.Update.
func (c *CalibratedClocks) Update() (Parameters, bool, Parameters, bool) {
monotonicParams, monotonicOk := c.monotonic.Update()
realtimeParams, realtimeOk := c.realtime.Update()
return monotonicParams, monotonicOk, realtimeParams, realtimeOk
}
// GetTime implements Clocks.GetTime.
func (c *CalibratedClocks) GetTime(id ClockID) (int64, error) {
switch id {
case Monotonic:
return c.monotonic.GetTime()
case Realtime:
return c.realtime.GetTime()
default:
return 0, syserror.EINVAL
}
}