github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/probes/kernel_hz.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package probes 5 6 import ( 7 "bufio" 8 "errors" 9 "fmt" 10 "io" 11 "math" 12 "os" 13 "time" 14 ) 15 16 // Available CONFIG_HZ values, sorted from highest to lowest. 17 var hzValues = []uint16{1000, 300, 250, 100} 18 19 // KernelHZ attempts to estimate the kernel's CONFIG_HZ compile-time value by 20 // making snapshots of the kernel timestamp with a time interval in between. 21 // 22 // Blocks for at least 100ms while the measurement is in progress. Can block 23 // significantly longer under some hypervisors like VirtualBox due to buggy 24 // clocks, interrupt coalescing and low timer resolution. 25 func KernelHZ() (uint16, error) { 26 f, err := os.Open("/proc/schedstat") 27 if err != nil { 28 return 0, err 29 } 30 defer f.Close() 31 32 // Measure the kernel timestamp at least 100ms apart, giving kernel timer and 33 // wall clock ample opportunity to advance for adequate sample size. 34 j1, err := readSchedstat(f) 35 if err != nil { 36 return 0, err 37 } 38 39 // On some platforms, this can put the goroutine to sleep for significantly 40 // longer than 100ms. Do not rely on readings being anywhere near 100ms apart. 41 time.Sleep(time.Millisecond * 100) 42 43 j2, err := readSchedstat(f) 44 if err != nil { 45 return 0, err 46 } 47 48 hz, err := j1.interpolate(j2) 49 if err != nil { 50 return 0, fmt.Errorf("interpolating hz value: %w", err) 51 } 52 53 return nearest(hz, hzValues) 54 } 55 56 // Jiffies returns the kernel's internal timestamp in jiffies read from 57 // /proc/schedstat. 58 func Jiffies() (uint64, error) { 59 f, err := os.Open("/proc/schedstat") 60 if err != nil { 61 return 0, err 62 } 63 defer f.Close() 64 65 k, err := readSchedstat(f) 66 if err != nil { 67 return 0, err 68 } 69 70 return k.k, nil 71 } 72 73 // readSchedstat expects to read /proc/schedstat and returns the first line 74 // matching 'timestamp %d'. Upon return, f is rewound to allow reuse. 75 // 76 // Should not be called concurrently. 77 func readSchedstat(f io.ReadSeeker) (ktime, error) { 78 // Rewind the file when done so the next call gets fresh data. 79 defer func() { _, _ = f.Seek(0, 0) }() 80 81 var j uint64 82 var t = time.Now() 83 84 s := bufio.NewScanner(f) 85 for s.Scan() { 86 if _, err := fmt.Sscanf(s.Text(), "timestamp %d", &j); err == nil { 87 return ktime{j, t}, nil 88 } 89 } 90 91 return ktime{}, errors.New("no kernel timestamp found") 92 } 93 94 type ktime struct { 95 k uint64 96 t time.Time 97 } 98 99 // interpolate returns the amount of jiffies (ktime) that would have elapsed if 100 // both ktimes were measured exactly 1 second apart. Using linear interpolation, 101 // the delta between both kernel timestamps is adjusted based on the elapsed 102 // wall time between both measurements. 103 func (old ktime) interpolate(new ktime) (uint16, error) { 104 if old.t.After(new.t) { 105 return 0, fmt.Errorf("old wall time %v is more recent than %v", old.t, new.t) 106 } 107 if old.k > new.k { 108 return 0, fmt.Errorf("old kernel timer %d is higher than %d", old.k, new.k) 109 } 110 111 // Jiffy and duration delta. 112 kd := new.k - old.k 113 td := new.t.Sub(old.t) 114 115 // Linear interpolation to represent elapsed jiffies as a per-second value. 116 hz := float64(kd) / td.Seconds() 117 hz = math.Round(hz) 118 if hz > math.MaxUint16 { 119 return 0, fmt.Errorf("interpolated hz value would overflow uint16: %f", hz) 120 } 121 122 return uint16(hz), nil 123 } 124 125 // nearest returns the entry from values that's closest to in. If in has an 126 // equal distance to multiple values, the value that appears the earliest in 127 // values wins. Returns error if values is empty. 128 func nearest(in uint16, values []uint16) (uint16, error) { 129 if len(values) == 0 { 130 return 0, errors.New("values cannot be empty") 131 } 132 133 var out uint16 134 min := ^uint16(0) 135 for _, v := range values { 136 // Get absolute distance between in and v. 137 d := uint16(in - v) 138 if in < v { 139 d = v - in 140 } 141 142 // Check if the distance to the current number is smaller than to the 143 // previous number. 144 if d < min { 145 min = d 146 out = v 147 } 148 } 149 150 return out, nil 151 }