cpu_linux.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //go:build linux
  2. // +build linux
  3. package cpu
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "github.com/shirou/gopsutil/v3/internal/common"
  12. "github.com/tklauser/go-sysconf"
  13. )
  14. var ClocksPerSec = float64(100)
  15. func init() {
  16. clkTck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
  17. // ignore errors
  18. if err == nil {
  19. ClocksPerSec = float64(clkTck)
  20. }
  21. }
  22. func Times(percpu bool) ([]TimesStat, error) {
  23. return TimesWithContext(context.Background(), percpu)
  24. }
  25. func TimesWithContext(ctx context.Context, percpu bool) ([]TimesStat, error) {
  26. filename := common.HostProc("stat")
  27. lines := []string{}
  28. if percpu {
  29. statlines, err := common.ReadLines(filename)
  30. if err != nil || len(statlines) < 2 {
  31. return []TimesStat{}, nil
  32. }
  33. for _, line := range statlines[1:] {
  34. if !strings.HasPrefix(line, "cpu") {
  35. break
  36. }
  37. lines = append(lines, line)
  38. }
  39. } else {
  40. lines, _ = common.ReadLinesOffsetN(filename, 0, 1)
  41. }
  42. ret := make([]TimesStat, 0, len(lines))
  43. for _, line := range lines {
  44. ct, err := parseStatLine(line)
  45. if err != nil {
  46. continue
  47. }
  48. ret = append(ret, *ct)
  49. }
  50. return ret, nil
  51. }
  52. func sysCPUPath(cpu int32, relPath string) string {
  53. return common.HostSys(fmt.Sprintf("devices/system/cpu/cpu%d", cpu), relPath)
  54. }
  55. func finishCPUInfo(c *InfoStat) {
  56. var lines []string
  57. var err error
  58. var value float64
  59. if len(c.CoreID) == 0 {
  60. lines, err = common.ReadLines(sysCPUPath(c.CPU, "topology/core_id"))
  61. if err == nil {
  62. c.CoreID = lines[0]
  63. }
  64. }
  65. // override the value of c.Mhz with cpufreq/cpuinfo_max_freq regardless
  66. // of the value from /proc/cpuinfo because we want to report the maximum
  67. // clock-speed of the CPU for c.Mhz, matching the behaviour of Windows
  68. lines, err = common.ReadLines(sysCPUPath(c.CPU, "cpufreq/cpuinfo_max_freq"))
  69. // if we encounter errors below such as there are no cpuinfo_max_freq file,
  70. // we just ignore. so let Mhz is 0.
  71. if err != nil || len(lines) == 0 {
  72. return
  73. }
  74. value, err = strconv.ParseFloat(lines[0], 64)
  75. if err != nil {
  76. return
  77. }
  78. c.Mhz = value / 1000.0 // value is in kHz
  79. if c.Mhz > 9999 {
  80. c.Mhz = c.Mhz / 1000.0 // value in Hz
  81. }
  82. }
  83. // CPUInfo on linux will return 1 item per physical thread.
  84. //
  85. // CPUs have three levels of counting: sockets, cores, threads.
  86. // Cores with HyperThreading count as having 2 threads per core.
  87. // Sockets often come with many physical CPU cores.
  88. // For example a single socket board with two cores each with HT will
  89. // return 4 CPUInfoStat structs on Linux and the "Cores" field set to 1.
  90. func Info() ([]InfoStat, error) {
  91. return InfoWithContext(context.Background())
  92. }
  93. func InfoWithContext(ctx context.Context) ([]InfoStat, error) {
  94. filename := common.HostProc("cpuinfo")
  95. lines, _ := common.ReadLines(filename)
  96. var ret []InfoStat
  97. var processorName string
  98. c := InfoStat{CPU: -1, Cores: 1}
  99. for _, line := range lines {
  100. fields := strings.Split(line, ":")
  101. if len(fields) < 2 {
  102. continue
  103. }
  104. key := strings.TrimSpace(fields[0])
  105. value := strings.TrimSpace(fields[1])
  106. switch key {
  107. case "Processor":
  108. processorName = value
  109. case "processor":
  110. if c.CPU >= 0 {
  111. finishCPUInfo(&c)
  112. ret = append(ret, c)
  113. }
  114. c = InfoStat{Cores: 1, ModelName: processorName}
  115. t, err := strconv.ParseInt(value, 10, 64)
  116. if err != nil {
  117. return ret, err
  118. }
  119. c.CPU = int32(t)
  120. case "vendorId", "vendor_id":
  121. c.VendorID = value
  122. case "CPU implementer":
  123. if v, err := strconv.ParseUint(value, 0, 8); err == nil {
  124. switch v {
  125. case 0x41:
  126. c.VendorID = "ARM"
  127. case 0x42:
  128. c.VendorID = "Broadcom"
  129. case 0x43:
  130. c.VendorID = "Cavium"
  131. case 0x44:
  132. c.VendorID = "DEC"
  133. case 0x46:
  134. c.VendorID = "Fujitsu"
  135. case 0x48:
  136. c.VendorID = "HiSilicon"
  137. case 0x49:
  138. c.VendorID = "Infineon"
  139. case 0x4d:
  140. c.VendorID = "Motorola/Freescale"
  141. case 0x4e:
  142. c.VendorID = "NVIDIA"
  143. case 0x50:
  144. c.VendorID = "APM"
  145. case 0x51:
  146. c.VendorID = "Qualcomm"
  147. case 0x56:
  148. c.VendorID = "Marvell"
  149. case 0x61:
  150. c.VendorID = "Apple"
  151. case 0x69:
  152. c.VendorID = "Intel"
  153. case 0xc0:
  154. c.VendorID = "Ampere"
  155. }
  156. }
  157. case "cpu family":
  158. c.Family = value
  159. case "model", "CPU part":
  160. c.Model = value
  161. case "model name", "cpu":
  162. c.ModelName = value
  163. if strings.Contains(value, "POWER8") ||
  164. strings.Contains(value, "POWER7") {
  165. c.Model = strings.Split(value, " ")[0]
  166. c.Family = "POWER"
  167. c.VendorID = "IBM"
  168. }
  169. case "stepping", "revision", "CPU revision":
  170. val := value
  171. if key == "revision" {
  172. val = strings.Split(value, ".")[0]
  173. }
  174. t, err := strconv.ParseInt(val, 10, 64)
  175. if err != nil {
  176. return ret, err
  177. }
  178. c.Stepping = int32(t)
  179. case "cpu MHz", "clock":
  180. // treat this as the fallback value, thus we ignore error
  181. if t, err := strconv.ParseFloat(strings.Replace(value, "MHz", "", 1), 64); err == nil {
  182. c.Mhz = t
  183. }
  184. case "cache size":
  185. t, err := strconv.ParseInt(strings.Replace(value, " KB", "", 1), 10, 64)
  186. if err != nil {
  187. return ret, err
  188. }
  189. c.CacheSize = int32(t)
  190. case "physical id":
  191. c.PhysicalID = value
  192. case "core id":
  193. c.CoreID = value
  194. case "flags", "Features":
  195. c.Flags = strings.FieldsFunc(value, func(r rune) bool {
  196. return r == ',' || r == ' '
  197. })
  198. case "microcode":
  199. c.Microcode = value
  200. }
  201. }
  202. if c.CPU >= 0 {
  203. finishCPUInfo(&c)
  204. ret = append(ret, c)
  205. }
  206. return ret, nil
  207. }
  208. func parseStatLine(line string) (*TimesStat, error) {
  209. fields := strings.Fields(line)
  210. if len(fields) == 0 {
  211. return nil, errors.New("stat does not contain cpu info")
  212. }
  213. if !strings.HasPrefix(fields[0], "cpu") {
  214. return nil, errors.New("not contain cpu")
  215. }
  216. cpu := fields[0]
  217. if cpu == "cpu" {
  218. cpu = "cpu-total"
  219. }
  220. user, err := strconv.ParseFloat(fields[1], 64)
  221. if err != nil {
  222. return nil, err
  223. }
  224. nice, err := strconv.ParseFloat(fields[2], 64)
  225. if err != nil {
  226. return nil, err
  227. }
  228. system, err := strconv.ParseFloat(fields[3], 64)
  229. if err != nil {
  230. return nil, err
  231. }
  232. idle, err := strconv.ParseFloat(fields[4], 64)
  233. if err != nil {
  234. return nil, err
  235. }
  236. iowait, err := strconv.ParseFloat(fields[5], 64)
  237. if err != nil {
  238. return nil, err
  239. }
  240. irq, err := strconv.ParseFloat(fields[6], 64)
  241. if err != nil {
  242. return nil, err
  243. }
  244. softirq, err := strconv.ParseFloat(fields[7], 64)
  245. if err != nil {
  246. return nil, err
  247. }
  248. ct := &TimesStat{
  249. CPU: cpu,
  250. User: user / ClocksPerSec,
  251. Nice: nice / ClocksPerSec,
  252. System: system / ClocksPerSec,
  253. Idle: idle / ClocksPerSec,
  254. Iowait: iowait / ClocksPerSec,
  255. Irq: irq / ClocksPerSec,
  256. Softirq: softirq / ClocksPerSec,
  257. }
  258. if len(fields) > 8 { // Linux >= 2.6.11
  259. steal, err := strconv.ParseFloat(fields[8], 64)
  260. if err != nil {
  261. return nil, err
  262. }
  263. ct.Steal = steal / ClocksPerSec
  264. }
  265. if len(fields) > 9 { // Linux >= 2.6.24
  266. guest, err := strconv.ParseFloat(fields[9], 64)
  267. if err != nil {
  268. return nil, err
  269. }
  270. ct.Guest = guest / ClocksPerSec
  271. }
  272. if len(fields) > 10 { // Linux >= 3.2.0
  273. guestNice, err := strconv.ParseFloat(fields[10], 64)
  274. if err != nil {
  275. return nil, err
  276. }
  277. ct.GuestNice = guestNice / ClocksPerSec
  278. }
  279. return ct, nil
  280. }
  281. func CountsWithContext(ctx context.Context, logical bool) (int, error) {
  282. if logical {
  283. ret := 0
  284. // https://github.com/giampaolo/psutil/blob/d01a9eaa35a8aadf6c519839e987a49d8be2d891/psutil/_pslinux.py#L599
  285. procCpuinfo := common.HostProc("cpuinfo")
  286. lines, err := common.ReadLines(procCpuinfo)
  287. if err == nil {
  288. for _, line := range lines {
  289. line = strings.ToLower(line)
  290. if strings.HasPrefix(line, "processor") {
  291. _, err = strconv.Atoi(strings.TrimSpace(line[strings.IndexByte(line, ':')+1:]))
  292. if err == nil {
  293. ret++
  294. }
  295. }
  296. }
  297. }
  298. if ret == 0 {
  299. procStat := common.HostProc("stat")
  300. lines, err = common.ReadLines(procStat)
  301. if err != nil {
  302. return 0, err
  303. }
  304. for _, line := range lines {
  305. if len(line) >= 4 && strings.HasPrefix(line, "cpu") && '0' <= line[3] && line[3] <= '9' { // `^cpu\d` regexp matching
  306. ret++
  307. }
  308. }
  309. }
  310. return ret, nil
  311. }
  312. // physical cores
  313. // https://github.com/giampaolo/psutil/blob/8415355c8badc9c94418b19bdf26e622f06f0cce/psutil/_pslinux.py#L615-L628
  314. threadSiblingsLists := make(map[string]bool)
  315. // These 2 files are the same but */core_cpus_list is newer while */thread_siblings_list is deprecated and may disappear in the future.
  316. // https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
  317. // https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
  318. // https://lkml.org/lkml/2019/2/26/41
  319. for _, glob := range []string{"devices/system/cpu/cpu[0-9]*/topology/core_cpus_list", "devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"} {
  320. if files, err := filepath.Glob(common.HostSys(glob)); err == nil {
  321. for _, file := range files {
  322. lines, err := common.ReadLines(file)
  323. if err != nil || len(lines) != 1 {
  324. continue
  325. }
  326. threadSiblingsLists[lines[0]] = true
  327. }
  328. ret := len(threadSiblingsLists)
  329. if ret != 0 {
  330. return ret, nil
  331. }
  332. }
  333. }
  334. // https://github.com/giampaolo/psutil/blob/122174a10b75c9beebe15f6c07dcf3afbe3b120d/psutil/_pslinux.py#L631-L652
  335. filename := common.HostProc("cpuinfo")
  336. lines, err := common.ReadLines(filename)
  337. if err != nil {
  338. return 0, err
  339. }
  340. mapping := make(map[int]int)
  341. currentInfo := make(map[string]int)
  342. for _, line := range lines {
  343. line = strings.ToLower(strings.TrimSpace(line))
  344. if line == "" {
  345. // new section
  346. id, okID := currentInfo["physical id"]
  347. cores, okCores := currentInfo["cpu cores"]
  348. if okID && okCores {
  349. mapping[id] = cores
  350. }
  351. currentInfo = make(map[string]int)
  352. continue
  353. }
  354. fields := strings.Split(line, ":")
  355. if len(fields) < 2 {
  356. continue
  357. }
  358. fields[0] = strings.TrimSpace(fields[0])
  359. if fields[0] == "physical id" || fields[0] == "cpu cores" {
  360. val, err := strconv.Atoi(strings.TrimSpace(fields[1]))
  361. if err != nil {
  362. continue
  363. }
  364. currentInfo[fields[0]] = val
  365. }
  366. }
  367. ret := 0
  368. for _, v := range mapping {
  369. ret += v
  370. }
  371. return ret, nil
  372. }