github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/time/sampler.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  	"errors"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/log"
    21  )
    22  
    23  const (
    24  	// maxSampleLoops is the maximum number of times to try to get a clock sample
    25  	// under the expected overhead.
    26  	maxSampleLoops = 5
    27  
    28  	// maxSamples is the maximum number of samples to collect.
    29  	maxSamples = 10
    30  )
    31  
    32  // errOverheadTooHigh is returned from sampler.Sample if the syscall
    33  // overhead is too high.
    34  var errOverheadTooHigh = errors.New("time syscall overhead exceeds maximum")
    35  
    36  // TSCValue is a value from the TSC.
    37  type TSCValue int64
    38  
    39  // Rdtsc reads the TSC.
    40  //
    41  // Intel SDM, Vol 3, Ch 17.15:
    42  // "The RDTSC instruction reads the time-stamp counter and is guaranteed to
    43  // return a monotonically increasing unique value whenever executed, except for
    44  // a 64-bit counter wraparound. Intel guarantees that the time-stamp counter
    45  // will not wraparound within 10 years after being reset."
    46  //
    47  // We use int64, so we have 5 years before wrap-around.
    48  func Rdtsc() TSCValue
    49  
    50  // ReferenceNS are nanoseconds in the reference clock domain.
    51  // int64 gives us ~290 years before this overflows.
    52  type ReferenceNS int64
    53  
    54  // Magnitude returns the absolute value of r.
    55  func (r ReferenceNS) Magnitude() ReferenceNS {
    56  	if r < 0 {
    57  		return -r
    58  	}
    59  	return r
    60  }
    61  
    62  // cycleClock is a TSC-based cycle clock.
    63  type cycleClock interface {
    64  	// Cycles returns a count value from the TSC.
    65  	Cycles() TSCValue
    66  }
    67  
    68  // tscCycleClock is a cycleClock that uses the real TSC.
    69  type tscCycleClock struct{}
    70  
    71  // Cycles implements cycleClock.Cycles.
    72  func (tscCycleClock) Cycles() TSCValue {
    73  	return Rdtsc()
    74  }
    75  
    76  // sample contains a sample from the reference clock, with TSC values from
    77  // before and after the reference clock value was captured.
    78  type sample struct {
    79  	before TSCValue
    80  	after  TSCValue
    81  	ref    ReferenceNS
    82  }
    83  
    84  // Overhead returns the sample overhead in TSC cycles.
    85  func (s *sample) Overhead() TSCValue {
    86  	return s.after - s.before
    87  }
    88  
    89  // referenceClocks collects individual samples from a reference clock ID and
    90  // TSC.
    91  type referenceClocks interface {
    92  	cycleClock
    93  
    94  	// Sample returns a single sample from the reference clock ID.
    95  	Sample(c ClockID) (sample, error)
    96  }
    97  
    98  // sampler collects samples from a reference system clock, minimizing
    99  // the overhead in each sample.
   100  type sampler struct {
   101  	// clockID is the reference clock ID (e.g., CLOCK_MONOTONIC).
   102  	clockID ClockID
   103  
   104  	// clocks provides raw samples.
   105  	clocks referenceClocks
   106  
   107  	// overhead is the estimated sample overhead in TSC cycles.
   108  	overhead TSCValue
   109  
   110  	// samples is a ring buffer of the latest samples collected.
   111  	samples []sample
   112  }
   113  
   114  // newSampler creates a sampler for clockID.
   115  func newSampler(c ClockID) *sampler {
   116  	return &sampler{
   117  		clockID:  c,
   118  		clocks:   syscallTSCReferenceClocks{},
   119  		overhead: defaultOverheadCycles,
   120  	}
   121  }
   122  
   123  // Reset discards previously collected clock samples.
   124  func (s *sampler) Reset() {
   125  	s.overhead = defaultOverheadCycles
   126  	s.samples = []sample{}
   127  }
   128  
   129  // lowOverheadSample returns a reference clock sample with minimized syscall overhead.
   130  func (s *sampler) lowOverheadSample() (sample, error) {
   131  	for {
   132  		for i := 0; i < maxSampleLoops; i++ {
   133  			samp, err := s.clocks.Sample(s.clockID)
   134  			if err != nil {
   135  				return sample{}, err
   136  			}
   137  
   138  			if samp.before > samp.after {
   139  				log.Warningf("TSC went backwards: %v > %v", samp.before, samp.after)
   140  				continue
   141  			}
   142  
   143  			if samp.Overhead() <= s.overhead {
   144  				return samp, nil
   145  			}
   146  		}
   147  
   148  		// Couldn't get a sample with the current overhead. Increase it.
   149  		newOverhead := 2 * s.overhead
   150  		if newOverhead > maxOverheadCycles {
   151  			// We'll give it one more shot with the max overhead.
   152  
   153  			if s.overhead == maxOverheadCycles {
   154  				return sample{}, errOverheadTooHigh
   155  			}
   156  
   157  			newOverhead = maxOverheadCycles
   158  		}
   159  
   160  		s.overhead = newOverhead
   161  		log.Debugf("Time: Adjusting syscall overhead up to %v", s.overhead)
   162  	}
   163  }
   164  
   165  // Sample collects a reference clock sample.
   166  func (s *sampler) Sample() error {
   167  	sample, err := s.lowOverheadSample()
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	s.samples = append(s.samples, sample)
   173  	if len(s.samples) > maxSamples {
   174  		s.samples = s.samples[1:]
   175  	}
   176  
   177  	// If the 4 most recent samples all have an overhead less than half the
   178  	// expected overhead, adjust downwards.
   179  	if len(s.samples) < 4 {
   180  		return nil
   181  	}
   182  
   183  	for _, sample := range s.samples[len(s.samples)-4:] {
   184  		if sample.Overhead() > s.overhead/2 {
   185  			return nil
   186  		}
   187  	}
   188  
   189  	s.overhead -= s.overhead / 8
   190  	log.Debugf("Time: Adjusting syscall overhead down to %v", s.overhead)
   191  
   192  	return nil
   193  }
   194  
   195  // Syscall returns the current raw reference time without storing TSC
   196  // samples.
   197  func (s *sampler) Syscall() (ReferenceNS, error) {
   198  	sample, err := s.clocks.Sample(s.clockID)
   199  	if err != nil {
   200  		return 0, err
   201  	}
   202  
   203  	return sample.ref, nil
   204  }
   205  
   206  // Cycles returns a raw TSC value.
   207  func (s *sampler) Cycles() TSCValue {
   208  	return s.clocks.Cycles()
   209  }
   210  
   211  // Range returns the widest range of clock samples available.
   212  func (s *sampler) Range() (sample, sample, bool) {
   213  	if len(s.samples) < 2 {
   214  		return sample{}, sample{}, false
   215  	}
   216  
   217  	return s.samples[0], s.samples[len(s.samples)-1], true
   218  }