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  }