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