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