cpu_linux.go 9.9 KB

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