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 }