1
2
3 package cpu
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "path/filepath"
10 "strconv"
11 "strings"
12
13 "github.com/shirou/gopsutil/internal/common"
14 "github.com/tklauser/go-sysconf"
15 )
16
17 var ClocksPerSec = float64(100)
18
19 func init() {
20 clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
21
22 if err == nil {
23 ClocksPerSec = float64(clkTck)
24 }
25 }
26
27 func Times(percpu bool) ([]TimesStat, error) {
28 return TimesWithContext(context.Background(), percpu)
29 }
30
31 func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
32 filename := common.HostProc("stat")
33 var lines = []string{}
34 if percpu {
35 statlines, err := common.ReadLines(filename)
36 if err != nil || len(statlines) < 2 {
37 return []TimesStat{}, nil
38 }
39 for _, line := range statlines[1:] {
40 if !strings.HasPrefix(line, "cpu") {
41 break
42 }
43 lines = append(lines, line)
44 }
45 } else {
46 lines, _ = common.ReadLinesOffsetN(filename, 0, 1)
47 }
48
49 ret := make([]TimesStat, 0, len(lines))
50
51 for _, line := range lines {
52 ct, err := parseStatLine(line)
53 if err != nil {
54 continue
55 }
56 ret = append(ret, *ct)
57
58 }
59 return ret, nil
60 }
61
62 func sysCPUPath(cpu int32, relPath string) string {
63 return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath)
64 }
65
66 func finishCPUInfo(c *InfoStat) error {
67 var lines []string
68 var err error
69 var value float64
70
71 if len(c.CoreID) == 0 {
72 lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id"))
73 if err == nil {
74 c.CoreID = lines[0]
75 }
76 }
77
78
79
80
81 lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
82
83
84 if err != nil || len(lines) == 0 {
85 return nil
86 }
87 value, err = strconv.ParseFloat(lines[0], 64)
88 if err != nil {
89 return nil
90 }
91 c.Mhz = value / 1000.0
92 if c.Mhz > 9999 {
93 c.Mhz = c.Mhz / 1000.0
94 }
95 return nil
96 }
97
98
99
100
101
102
103
104
105 func Info() ([]InfoStat, error) {
106 return InfoWithContext(context.Background())
107 }
108
109 func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
110 filename := common.HostProc("cpuinfo")
111 lines, _ := common.ReadLines(filename)
112
113 var ret []InfoStat
114 var processorName string
115
116 c := InfoStat{CPU: -1, Cores: 1}
117 for _, line := range lines {
118 fields := strings.Split(line, ":")
119 if len(fields) < 2 {
120 continue
121 }
122 key := strings.TrimSpace(fields[0])
123 value := strings.TrimSpace(fields[1])
124
125 switch key {
126 case "Processor":
127 processorName = value
128 case "processor":
129 if c.CPU >= 0 {
130 err := finishCPUInfo(&c)
131 if err != nil {
132 return ret, err
133 }
134 ret = append(ret, c)
135 }
136 c = InfoStat{Cores: 1, ModelName: processorName}
137 t, err := strconv.ParseInt(value, 10, 64)
138 if err != nil {
139 return ret, err
140 }
141 c.CPU = int32(t)
142 case "vendorId", "vendor_id":
143 c.VendorID = value
144 case "CPU implementer":
145 if v, err := strconv.ParseUint(value, 0, 8); err == nil {
146 switch v {
147 case 0x41:
148 c.VendorID = "ARM"
149 case 0x42:
150 c.VendorID = "Broadcom"
151 case 0x43:
152 c.VendorID = "Cavium"
153 case 0x44:
154 c.VendorID = "DEC"
155 case 0x46:
156 c.VendorID = "Fujitsu"
157 case 0x48:
158 c.VendorID = "HiSilicon"
159 case 0x49:
160 c.VendorID = "Infineon"
161 case 0x4d:
162 c.VendorID = "Motorola/Freescale"
163 case 0x4e:
164 c.VendorID = "NVIDIA"
165 case 0x50:
166 c.VendorID = "APM"
167 case 0x51:
168 c.VendorID = "Qualcomm"
169 case 0x56:
170 c.VendorID = "Marvell"
171 case 0x61:
172 c.VendorID = "Apple"
173 case 0x69:
174 c.VendorID = "Intel"
175 case 0xc0:
176 c.VendorID = "Ampere"
177 }
178 }
179 case "cpu family":
180 c.Family = value
181 case "model", "CPU part":
182 c.Model = value
183 case "model name", "cpu":
184 c.ModelName = value
185 if strings.Contains(value, "POWER8") ||
186 strings.Contains(value, "POWER7") {
187 c.Model = strings.Split(value, " ")[0]
188 c.Family = "POWER"
189 c.VendorID = "IBM"
190 }
191 case "stepping", "revision", "CPU revision":
192 val := value
193
194 if key == "revision" {
195 val = strings.Split(value, ".")[0]
196 }
197
198 t, err := strconv.ParseInt(val, 10, 64)
199 if err != nil {
200 return ret, err
201 }
202 c.Stepping = int32(t)
203 case "cpu MHz", "clock":
204
205 if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
206 c.Mhz = t
207 }
208 case "cache size":
209 t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
210 if err != nil {
211 return ret, err
212 }
213 c.CacheSize = int32(t)
214 case "physical id":
215 c.PhysicalID = value
216 case "core id":
217 c.CoreID = value
218 case "flags", "Features":
219 c.Flags = strings.FieldsFunc(value, func(r rune) bool {
220 return r == ',' || r == ' '
221 })
222 case "microcode":
223 c.Microcode = value
224 }
225 }
226 if c.CPU >= 0 {
227 err := finishCPUInfo(&c)
228 if err != nil {
229 return ret, err
230 }
231 ret = append(ret, c)
232 }
233 return ret, nil
234 }
235
236 func parseStatLine(line string) (*TimesStat, error) {
237 fields := strings.Fields(line)
238
239 if len(fields) == 0 {
240 return nil, errors.New("stat does not contain cpu info")
241 }
242
243 if strings.HasPrefix(fields[0], "cpu") == false {
244 return nil, errors.New("not contain cpu")
245 }
246
247 cpu := fields[0]
248 if cpu == "cpu" {
249 cpu = "cpu-total"
250 }
251 user, err := strconv.ParseFloat(fields[1], 64)
252 if err != nil {
253 return nil, err
254 }
255 nice, err := strconv.ParseFloat(fields[2], 64)
256 if err != nil {
257 return nil, err
258 }
259 system, err := strconv.ParseFloat(fields[3], 64)
260 if err != nil {
261 return nil, err
262 }
263 idle, err := strconv.ParseFloat(fields[4], 64)
264 if err != nil {
265 return nil, err
266 }
267 iowait, err := strconv.ParseFloat(fields[5], 64)
268 if err != nil {
269 return nil, err
270 }
271 irq, err := strconv.ParseFloat(fields[6], 64)
272 if err != nil {
273 return nil, err
274 }
275 softirq, err := strconv.ParseFloat(fields[7], 64)
276 if err != nil {
277 return nil, err
278 }
279
280 ct := &TimesStat{
281 CPU: cpu,
282 User: user / ClocksPerSec,
283 Nice: nice / ClocksPerSec,
284 System: system / ClocksPerSec,
285 Idle: idle / ClocksPerSec,
286 Iowait: iowait / ClocksPerSec,
287 Irq: irq / ClocksPerSec,
288 Softirq: softirq / ClocksPerSec,
289 }
290 if len(fields) > 8 {
291 steal, err := strconv.ParseFloat(fields[8], 64)
292 if err != nil {
293 return nil, err
294 }
295 ct.Steal = steal / ClocksPerSec
296 }
297 if len(fields) > 9 {
298 guest, err := strconv.ParseFloat(fields[9], 64)
299 if err != nil {
300 return nil, err
301 }
302 ct.Guest = guest / ClocksPerSec
303 }
304 if len(fields) > 10 {
305 guestNice, err := strconv.ParseFloat(fields[10], 64)
306 if err != nil {
307 return nil, err
308 }
309 ct.GuestNice = guestNice / ClocksPerSec
310 }
311
312 return ct, nil
313 }
314
315 func CountsWithContext(ctx context.Context, logical bool) (int, error) {
316 if logical {
317 ret := 0
318
319 procCpuinfo := common.HostProc("cpuinfo")
320 lines, err := common.ReadLines(procCpuinfo)
321 if err == nil {
322 for _, line := range lines {
323 line = strings.ToLower(line)
324 if strings.HasPrefix(line, "processor") {
325 _, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]))
326 if err == nil {
327 ret++
328 }
329 }
330 }
331 }
332 if ret == 0 {
333 procStat := common.HostProc("stat")
334 lines, err = common.ReadLines(procStat)
335 if err != nil {
336 return 0, err
337 }
338 for _, line := range lines {
339 if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' {
340 ret++
341 }
342 }
343 }
344 return ret, nil
345 }
346
347
348 var threadSiblingsLists = make(map[string]bool)
349
350
351
352
353 for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} {
354 if files, err := filepath.Glob(common.HostSys(glob)); err == nil {
355 for _, file := range files {
356 lines, err := common.ReadLines(file)
357 if err != nil || len(lines) != 1 {
358 continue
359 }
360 threadSiblingsLists[lines[0]] = true
361 }
362 ret := len(threadSiblingsLists)
363 if ret != 0 {
364 return ret, nil
365 }
366 }
367 }
368
369 filename := common.HostProc("cpuinfo")
370 lines, err := common.ReadLines(filename)
371 if err != nil {
372 return 0, err
373 }
374 mapping := make(map[int]int)
375 currentInfo := make(map[string]int)
376 for _, line := range lines {
377 line = strings.ToLower(strings.TrimSpace(line))
378 if line == "" {
379
380 id, okID := currentInfo["physical id"]
381 cores, okCores := currentInfo["cpu cores"]
382 if okID && okCores {
383 mapping[id] = cores
384 }
385 currentInfo = make(map[string]int)
386 continue
387 }
388 fields := strings.Split(line, ":")
389 if len(fields) < 2 {
390 continue
391 }
392 fields[0] = strings.TrimSpace(fields[0])
393 if fields[0] == "physical id" || fields[0] == "cpu cores" {
394 val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
395 if err != nil {
396 continue
397 }
398 currentInfo[fields[0]] = val
399 }
400 }
401 ret := 0
402 for _, v := range mapping {
403 ret += v
404 }
405 return ret, nil
406 }
407
View as plain text