1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package sysfs
18
19 import (
20 "fmt"
21 "os"
22 "path/filepath"
23 "strconv"
24 "strings"
25
26 "golang.org/x/sync/errgroup"
27
28 "github.com/prometheus/procfs/internal/util"
29 )
30
31
32 type CPU string
33
34
35 func (c CPU) Number() string {
36 return strings.TrimPrefix(filepath.Base(string(c)), "cpu")
37 }
38
39
40 type CPUTopology struct {
41 CoreID string
42 CoreSiblingsList string
43 PhysicalPackageID string
44 ThreadSiblingsList string
45 }
46
47
48 type CPUThermalThrottle struct {
49 CoreThrottleCount uint64
50 PackageThrottleCount uint64
51 }
52
53
54 type SystemCPUCpufreqStats struct {
55 Name string
56 CpuinfoCurrentFrequency *uint64
57 CpuinfoMinimumFrequency *uint64
58 CpuinfoMaximumFrequency *uint64
59 CpuinfoTransitionLatency *uint64
60 ScalingCurrentFrequency *uint64
61 ScalingMinimumFrequency *uint64
62 ScalingMaximumFrequency *uint64
63 AvailableGovernors string
64 Driver string
65 Governor string
66 RelatedCpus string
67 SetSpeed string
68 }
69
70
71 func (fs FS) CPUs() ([]CPU, error) {
72 cpuPaths, err := filepath.Glob(fs.sys.Path("devices/system/cpu/cpu[0-9]*"))
73 if err != nil {
74 return nil, err
75 }
76 cpus := make([]CPU, len(cpuPaths))
77 for i, cpu := range cpuPaths {
78 cpus[i] = CPU(cpu)
79 }
80 return cpus, nil
81 }
82
83
84 func (c CPU) Topology() (*CPUTopology, error) {
85 cpuTopologyPath := filepath.Join(string(c), "topology")
86 if _, err := os.Stat(cpuTopologyPath); err != nil {
87 return nil, err
88 }
89 t, err := parseCPUTopology(cpuTopologyPath)
90 if err != nil {
91 return nil, err
92 }
93 return t, nil
94 }
95
96 func parseCPUTopology(cpuPath string) (*CPUTopology, error) {
97 t := CPUTopology{}
98 var err error
99 t.CoreID, err = util.SysReadFile(filepath.Join(cpuPath, "core_id"))
100 if err != nil {
101 return nil, err
102 }
103 t.PhysicalPackageID, err = util.SysReadFile(filepath.Join(cpuPath, "physical_package_id"))
104 if err != nil {
105 return nil, err
106 }
107 t.CoreSiblingsList, err = util.SysReadFile(filepath.Join(cpuPath, "core_siblings_list"))
108 if err != nil {
109 return nil, err
110 }
111 t.ThreadSiblingsList, err = util.SysReadFile(filepath.Join(cpuPath, "thread_siblings_list"))
112 if err != nil {
113 return nil, err
114 }
115 return &t, nil
116 }
117
118
119 func (c CPU) ThermalThrottle() (*CPUThermalThrottle, error) {
120 cpuPath := filepath.Join(string(c), "thermal_throttle")
121 if _, err := os.Stat(cpuPath); err != nil {
122 return nil, err
123 }
124 t, err := parseCPUThermalThrottle(cpuPath)
125 if err != nil {
126 return nil, err
127 }
128 return t, nil
129 }
130
131 func parseCPUThermalThrottle(cpuPath string) (*CPUThermalThrottle, error) {
132 t := CPUThermalThrottle{}
133 var err error
134 t.PackageThrottleCount, err = util.ReadUintFromFile(filepath.Join(cpuPath, "package_throttle_count"))
135 if err != nil {
136 return nil, err
137 }
138 t.CoreThrottleCount, err = util.ReadUintFromFile(filepath.Join(cpuPath, "core_throttle_count"))
139 if err != nil {
140 return nil, err
141 }
142 return &t, nil
143 }
144
145 func binSearch(elem uint16, elemSlice *[]uint16) bool {
146 if len(*elemSlice) == 0 {
147 return false
148 }
149
150 if len(*elemSlice) == 1 {
151 return elem == (*elemSlice)[0]
152 }
153
154 start := 0
155 end := len(*elemSlice) - 1
156
157 var mid int
158
159 for start <= end {
160 mid = (start + end) / 2
161 if (*elemSlice)[mid] == elem {
162 return true
163 } else if (*elemSlice)[mid] > elem {
164 end = mid - 1
165 } else if (*elemSlice)[mid] < elem {
166 start = mid + 1
167 }
168 }
169
170 return false
171 }
172
173 func filterOfflineCPUs(offlineCpus *[]uint16, cpus *[]string) ([]string, error) {
174 var filteredCPUs []string
175 for _, cpu := range *cpus {
176 cpuName := strings.TrimPrefix(filepath.Base(cpu), "cpu")
177 cpuNameUint16, err := strconv.Atoi(cpuName)
178 if err != nil {
179 return nil, err
180 }
181 if !binSearch(uint16(cpuNameUint16), offlineCpus) {
182 filteredCPUs = append(filteredCPUs, cpu)
183 }
184 }
185
186 return filteredCPUs, nil
187 }
188
189
190 func (fs FS) SystemCpufreq() ([]SystemCPUCpufreqStats, error) {
191 var g errgroup.Group
192
193 cpus, err := filepath.Glob(fs.sys.Path("devices/system/cpu/cpu[0-9]*"))
194 if err != nil {
195 return nil, err
196 }
197
198 line, err := util.ReadFileNoStat(fs.sys.Path("devices/system/cpu/offline"))
199 if err != nil {
200 return nil, err
201 }
202
203 if strings.TrimSpace(string(line)) != "" {
204 offlineCPUs, err := parseCPURange(line)
205 if err != nil {
206 return nil, err
207 }
208
209 if len(offlineCPUs) > 0 {
210 cpus, err = filterOfflineCPUs(&offlineCPUs, &cpus)
211 if err != nil {
212 return nil, err
213 }
214 }
215 }
216
217 systemCpufreq := make([]SystemCPUCpufreqStats, len(cpus))
218 for i, cpu := range cpus {
219 cpuName := strings.TrimPrefix(filepath.Base(cpu), "cpu")
220
221 cpuCpufreqPath := filepath.Join(cpu, "cpufreq")
222 _, err = os.Stat(cpuCpufreqPath)
223 if os.IsNotExist(err) {
224 continue
225 } else if err != nil {
226 return nil, err
227 }
228
229
230
231
232 i := i
233 g.Go(func() error {
234 cpufreq, err := parseCpufreqCpuinfo(cpuCpufreqPath)
235 if err == nil {
236 cpufreq.Name = cpuName
237 systemCpufreq[i] = *cpufreq
238 }
239 return err
240 })
241 }
242
243 if err = g.Wait(); err != nil {
244 return nil, err
245 }
246
247 if len(systemCpufreq) == 0 {
248 return nil, fmt.Errorf("could not find any cpufreq files: %w", os.ErrNotExist)
249 }
250
251 return systemCpufreq, nil
252 }
253
254 func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
255 uintFiles := []string{
256 "cpuinfo_cur_freq",
257 "cpuinfo_max_freq",
258 "cpuinfo_min_freq",
259 "cpuinfo_transition_latency",
260 "scaling_cur_freq",
261 "scaling_max_freq",
262 "scaling_min_freq",
263 }
264 uintOut := make([]*uint64, len(uintFiles))
265
266 for i, f := range uintFiles {
267 v, err := util.ReadUintFromFile(filepath.Join(cpuPath, f))
268 if err != nil {
269 if os.IsNotExist(err) || os.IsPermission(err) {
270 continue
271 }
272 return &SystemCPUCpufreqStats{}, err
273 }
274
275 uintOut[i] = &v
276 }
277
278 stringFiles := []string{
279 "scaling_available_governors",
280 "scaling_driver",
281 "scaling_governor",
282 "related_cpus",
283 "scaling_setspeed",
284 }
285 stringOut := make([]string, len(stringFiles))
286 var err error
287
288 for i, f := range stringFiles {
289 stringOut[i], err = util.SysReadFile(filepath.Join(cpuPath, f))
290 if err != nil {
291 return &SystemCPUCpufreqStats{}, err
292 }
293 }
294
295 return &SystemCPUCpufreqStats{
296 CpuinfoCurrentFrequency: uintOut[0],
297 CpuinfoMaximumFrequency: uintOut[1],
298 CpuinfoMinimumFrequency: uintOut[2],
299 CpuinfoTransitionLatency: uintOut[3],
300 ScalingCurrentFrequency: uintOut[4],
301 ScalingMaximumFrequency: uintOut[5],
302 ScalingMinimumFrequency: uintOut[6],
303 AvailableGovernors: stringOut[0],
304 Driver: stringOut[1],
305 Governor: stringOut[2],
306 RelatedCpus: stringOut[3],
307 SetSpeed: stringOut[4],
308 }, nil
309 }
310
311 func (fs FS) IsolatedCPUs() ([]uint16, error) {
312 isolcpus, err := os.ReadFile(fs.sys.Path("devices/system/cpu/isolated"))
313 if err != nil {
314 return nil, err
315 }
316
317 return parseCPURange(isolcpus)
318 }
319
320 func parseCPURange(data []byte) ([]uint16, error) {
321
322 var cpusInt = []uint16{}
323
324 for _, cpu := range strings.Split(strings.TrimRight(string(data), "\n"), ",") {
325 if cpu == "" {
326 continue
327 }
328 if strings.Contains(cpu, "-") {
329 ranges := strings.Split(cpu, "-")
330 if len(ranges) != 2 {
331 return nil, fmt.Errorf("invalid cpu range: %s", cpu)
332 }
333 startRange, err := strconv.Atoi(ranges[0])
334 if err != nil {
335 return nil, fmt.Errorf("invalid cpu start range: %w", err)
336 }
337 endRange, err := strconv.Atoi(ranges[1])
338 if err != nil {
339 return nil, fmt.Errorf("invalid cpu end range: %w", err)
340 }
341
342 for i := startRange; i <= endRange; i++ {
343 cpusInt = append(cpusInt, uint16(i))
344 }
345 continue
346 }
347
348 cpuN, err := strconv.Atoi(cpu)
349 if err != nil {
350 return nil, err
351 }
352 cpusInt = append(cpusInt, uint16(cpuN))
353 }
354 return cpusInt, nil
355 }
356
View as plain text