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 }