cpuinfo.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. // Copyright 2019 The Prometheus Authors
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. //go:build linux
  14. // +build linux
  15. package procfs
  16. import (
  17. "bufio"
  18. "bytes"
  19. "errors"
  20. "fmt"
  21. "regexp"
  22. "strconv"
  23. "strings"
  24. "github.com/prometheus/procfs/internal/util"
  25. )
  26. // CPUInfo contains general information about a system CPU found in /proc/cpuinfo.
  27. type CPUInfo struct {
  28. Processor uint
  29. VendorID string
  30. CPUFamily string
  31. Model string
  32. ModelName string
  33. Stepping string
  34. Microcode string
  35. CPUMHz float64
  36. CacheSize string
  37. PhysicalID string
  38. Siblings uint
  39. CoreID string
  40. CPUCores uint
  41. APICID string
  42. InitialAPICID string
  43. FPU string
  44. FPUException string
  45. CPUIDLevel uint
  46. WP string
  47. Flags []string
  48. Bugs []string
  49. BogoMips float64
  50. CLFlushSize uint
  51. CacheAlignment uint
  52. AddressSizes string
  53. PowerManagement string
  54. }
  55. var (
  56. cpuinfoClockRegexp = regexp.MustCompile(`([\d.]+)`)
  57. cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`)
  58. )
  59. // CPUInfo returns information about current system CPUs.
  60. // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
  61. func (fs FS) CPUInfo() ([]CPUInfo, error) {
  62. data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
  63. if err != nil {
  64. return nil, err
  65. }
  66. return parseCPUInfo(data)
  67. }
  68. func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
  69. scanner := bufio.NewScanner(bytes.NewReader(info))
  70. // find the first "processor" line
  71. firstLine := firstNonEmptyLine(scanner)
  72. if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
  73. return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, firstLine)
  74. }
  75. field := strings.SplitN(firstLine, ": ", 2)
  76. v, err := strconv.ParseUint(field[1], 0, 32)
  77. if err != nil {
  78. return nil, err
  79. }
  80. firstcpu := CPUInfo{Processor: uint(v)}
  81. cpuinfo := []CPUInfo{firstcpu}
  82. i := 0
  83. for scanner.Scan() {
  84. line := scanner.Text()
  85. if !strings.Contains(line, ":") {
  86. continue
  87. }
  88. field := strings.SplitN(line, ": ", 2)
  89. switch strings.TrimSpace(field[0]) {
  90. case "processor":
  91. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  92. i++
  93. v, err := strconv.ParseUint(field[1], 0, 32)
  94. if err != nil {
  95. return nil, err
  96. }
  97. cpuinfo[i].Processor = uint(v)
  98. case "vendor", "vendor_id":
  99. cpuinfo[i].VendorID = field[1]
  100. case "cpu family":
  101. cpuinfo[i].CPUFamily = field[1]
  102. case "model":
  103. cpuinfo[i].Model = field[1]
  104. case "model name":
  105. cpuinfo[i].ModelName = field[1]
  106. case "stepping":
  107. cpuinfo[i].Stepping = field[1]
  108. case "microcode":
  109. cpuinfo[i].Microcode = field[1]
  110. case "cpu MHz":
  111. v, err := strconv.ParseFloat(field[1], 64)
  112. if err != nil {
  113. return nil, err
  114. }
  115. cpuinfo[i].CPUMHz = v
  116. case "cache size":
  117. cpuinfo[i].CacheSize = field[1]
  118. case "physical id":
  119. cpuinfo[i].PhysicalID = field[1]
  120. case "siblings":
  121. v, err := strconv.ParseUint(field[1], 0, 32)
  122. if err != nil {
  123. return nil, err
  124. }
  125. cpuinfo[i].Siblings = uint(v)
  126. case "core id":
  127. cpuinfo[i].CoreID = field[1]
  128. case "cpu cores":
  129. v, err := strconv.ParseUint(field[1], 0, 32)
  130. if err != nil {
  131. return nil, err
  132. }
  133. cpuinfo[i].CPUCores = uint(v)
  134. case "apicid":
  135. cpuinfo[i].APICID = field[1]
  136. case "initial apicid":
  137. cpuinfo[i].InitialAPICID = field[1]
  138. case "fpu":
  139. cpuinfo[i].FPU = field[1]
  140. case "fpu_exception":
  141. cpuinfo[i].FPUException = field[1]
  142. case "cpuid level":
  143. v, err := strconv.ParseUint(field[1], 0, 32)
  144. if err != nil {
  145. return nil, err
  146. }
  147. cpuinfo[i].CPUIDLevel = uint(v)
  148. case "wp":
  149. cpuinfo[i].WP = field[1]
  150. case "flags":
  151. cpuinfo[i].Flags = strings.Fields(field[1])
  152. case "bugs":
  153. cpuinfo[i].Bugs = strings.Fields(field[1])
  154. case "bogomips":
  155. v, err := strconv.ParseFloat(field[1], 64)
  156. if err != nil {
  157. return nil, err
  158. }
  159. cpuinfo[i].BogoMips = v
  160. case "clflush size":
  161. v, err := strconv.ParseUint(field[1], 0, 32)
  162. if err != nil {
  163. return nil, err
  164. }
  165. cpuinfo[i].CLFlushSize = uint(v)
  166. case "cache_alignment":
  167. v, err := strconv.ParseUint(field[1], 0, 32)
  168. if err != nil {
  169. return nil, err
  170. }
  171. cpuinfo[i].CacheAlignment = uint(v)
  172. case "address sizes":
  173. cpuinfo[i].AddressSizes = field[1]
  174. case "power management":
  175. cpuinfo[i].PowerManagement = field[1]
  176. }
  177. }
  178. return cpuinfo, nil
  179. }
  180. func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
  181. scanner := bufio.NewScanner(bytes.NewReader(info))
  182. firstLine := firstNonEmptyLine(scanner)
  183. match, err := regexp.MatchString("^[Pp]rocessor", firstLine)
  184. if !match || !strings.Contains(firstLine, ":") {
  185. return nil, fmt.Errorf("%s: Cannot parse line: %q: %w", ErrFileParse, firstLine, err)
  186. }
  187. field := strings.SplitN(firstLine, ": ", 2)
  188. cpuinfo := []CPUInfo{}
  189. featuresLine := ""
  190. commonCPUInfo := CPUInfo{}
  191. i := 0
  192. if strings.TrimSpace(field[0]) == "Processor" {
  193. commonCPUInfo = CPUInfo{ModelName: field[1]}
  194. i = -1
  195. } else {
  196. v, err := strconv.ParseUint(field[1], 0, 32)
  197. if err != nil {
  198. return nil, err
  199. }
  200. firstcpu := CPUInfo{Processor: uint(v)}
  201. cpuinfo = []CPUInfo{firstcpu}
  202. }
  203. for scanner.Scan() {
  204. line := scanner.Text()
  205. if !strings.Contains(line, ":") {
  206. continue
  207. }
  208. field := strings.SplitN(line, ": ", 2)
  209. switch strings.TrimSpace(field[0]) {
  210. case "processor":
  211. cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
  212. i++
  213. v, err := strconv.ParseUint(field[1], 0, 32)
  214. if err != nil {
  215. return nil, err
  216. }
  217. cpuinfo[i].Processor = uint(v)
  218. case "BogoMIPS":
  219. if i == -1 {
  220. cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor
  221. i++
  222. cpuinfo[i].Processor = 0
  223. }
  224. v, err := strconv.ParseFloat(field[1], 64)
  225. if err != nil {
  226. return nil, err
  227. }
  228. cpuinfo[i].BogoMips = v
  229. case "Features":
  230. featuresLine = line
  231. case "model name":
  232. cpuinfo[i].ModelName = field[1]
  233. }
  234. }
  235. fields := strings.SplitN(featuresLine, ": ", 2)
  236. for i := range cpuinfo {
  237. cpuinfo[i].Flags = strings.Fields(fields[1])
  238. }
  239. return cpuinfo, nil
  240. }
  241. func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
  242. scanner := bufio.NewScanner(bytes.NewReader(info))
  243. firstLine := firstNonEmptyLine(scanner)
  244. if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
  245. return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, firstLine)
  246. }
  247. field := strings.SplitN(firstLine, ": ", 2)
  248. cpuinfo := []CPUInfo{}
  249. commonCPUInfo := CPUInfo{VendorID: field[1]}
  250. for scanner.Scan() {
  251. line := scanner.Text()
  252. if !strings.Contains(line, ":") {
  253. continue
  254. }
  255. field := strings.SplitN(line, ": ", 2)
  256. switch strings.TrimSpace(field[0]) {
  257. case "bogomips per cpu":
  258. v, err := strconv.ParseFloat(field[1], 64)
  259. if err != nil {
  260. return nil, err
  261. }
  262. commonCPUInfo.BogoMips = v
  263. case "features":
  264. commonCPUInfo.Flags = strings.Fields(field[1])
  265. }
  266. if strings.HasPrefix(line, "processor") {
  267. match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
  268. if len(match) < 2 {
  269. return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
  270. }
  271. cpu := commonCPUInfo
  272. v, err := strconv.ParseUint(match[1], 0, 32)
  273. if err != nil {
  274. return nil, err
  275. }
  276. cpu.Processor = uint(v)
  277. cpuinfo = append(cpuinfo, cpu)
  278. }
  279. if strings.HasPrefix(line, "cpu number") {
  280. break
  281. }
  282. }
  283. i := 0
  284. for scanner.Scan() {
  285. line := scanner.Text()
  286. if !strings.Contains(line, ":") {
  287. continue
  288. }
  289. field := strings.SplitN(line, ": ", 2)
  290. switch strings.TrimSpace(field[0]) {
  291. case "cpu number":
  292. i++
  293. case "cpu MHz dynamic":
  294. clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
  295. v, err := strconv.ParseFloat(clock, 64)
  296. if err != nil {
  297. return nil, err
  298. }
  299. cpuinfo[i].CPUMHz = v
  300. case "physical id":
  301. cpuinfo[i].PhysicalID = field[1]
  302. case "core id":
  303. cpuinfo[i].CoreID = field[1]
  304. case "cpu cores":
  305. v, err := strconv.ParseUint(field[1], 0, 32)
  306. if err != nil {
  307. return nil, err
  308. }
  309. cpuinfo[i].CPUCores = uint(v)
  310. case "siblings":
  311. v, err := strconv.ParseUint(field[1], 0, 32)
  312. if err != nil {
  313. return nil, err
  314. }
  315. cpuinfo[i].Siblings = uint(v)
  316. }
  317. }
  318. return cpuinfo, nil
  319. }
  320. func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
  321. scanner := bufio.NewScanner(bytes.NewReader(info))
  322. // find the first "processor" line
  323. firstLine := firstNonEmptyLine(scanner)
  324. if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
  325. return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
  326. }
  327. field := strings.SplitN(firstLine, ": ", 2)
  328. cpuinfo := []CPUInfo{}
  329. systemType := field[1]
  330. i := 0
  331. for scanner.Scan() {
  332. line := scanner.Text()
  333. if !strings.Contains(line, ":") {
  334. continue
  335. }
  336. field := strings.SplitN(line, ": ", 2)
  337. switch strings.TrimSpace(field[0]) {
  338. case "processor":
  339. v, err := strconv.ParseUint(field[1], 0, 32)
  340. if err != nil {
  341. return nil, err
  342. }
  343. i = int(v)
  344. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  345. cpuinfo[i].Processor = uint(v)
  346. cpuinfo[i].VendorID = systemType
  347. case "cpu model":
  348. cpuinfo[i].ModelName = field[1]
  349. case "BogoMIPS":
  350. v, err := strconv.ParseFloat(field[1], 64)
  351. if err != nil {
  352. return nil, err
  353. }
  354. cpuinfo[i].BogoMips = v
  355. }
  356. }
  357. return cpuinfo, nil
  358. }
  359. func parseCPUInfoLoong(info []byte) ([]CPUInfo, error) {
  360. scanner := bufio.NewScanner(bytes.NewReader(info))
  361. // find the first "processor" line
  362. firstLine := firstNonEmptyLine(scanner)
  363. if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
  364. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  365. }
  366. field := strings.SplitN(firstLine, ": ", 2)
  367. cpuinfo := []CPUInfo{}
  368. systemType := field[1]
  369. i := 0
  370. for scanner.Scan() {
  371. line := scanner.Text()
  372. if !strings.Contains(line, ":") {
  373. continue
  374. }
  375. field := strings.SplitN(line, ": ", 2)
  376. switch strings.TrimSpace(field[0]) {
  377. case "processor":
  378. v, err := strconv.ParseUint(field[1], 0, 32)
  379. if err != nil {
  380. return nil, err
  381. }
  382. i = int(v)
  383. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  384. cpuinfo[i].Processor = uint(v)
  385. cpuinfo[i].VendorID = systemType
  386. case "CPU Family":
  387. cpuinfo[i].CPUFamily = field[1]
  388. case "Model Name":
  389. cpuinfo[i].ModelName = field[1]
  390. }
  391. }
  392. return cpuinfo, nil
  393. }
  394. func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
  395. scanner := bufio.NewScanner(bytes.NewReader(info))
  396. firstLine := firstNonEmptyLine(scanner)
  397. if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
  398. return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
  399. }
  400. field := strings.SplitN(firstLine, ": ", 2)
  401. v, err := strconv.ParseUint(field[1], 0, 32)
  402. if err != nil {
  403. return nil, err
  404. }
  405. firstcpu := CPUInfo{Processor: uint(v)}
  406. cpuinfo := []CPUInfo{firstcpu}
  407. i := 0
  408. for scanner.Scan() {
  409. line := scanner.Text()
  410. if !strings.Contains(line, ":") {
  411. continue
  412. }
  413. field := strings.SplitN(line, ": ", 2)
  414. switch strings.TrimSpace(field[0]) {
  415. case "processor":
  416. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  417. i++
  418. v, err := strconv.ParseUint(field[1], 0, 32)
  419. if err != nil {
  420. return nil, err
  421. }
  422. cpuinfo[i].Processor = uint(v)
  423. case "cpu":
  424. cpuinfo[i].VendorID = field[1]
  425. case "clock":
  426. clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
  427. v, err := strconv.ParseFloat(clock, 64)
  428. if err != nil {
  429. return nil, err
  430. }
  431. cpuinfo[i].CPUMHz = v
  432. }
  433. }
  434. return cpuinfo, nil
  435. }
  436. func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) {
  437. scanner := bufio.NewScanner(bytes.NewReader(info))
  438. firstLine := firstNonEmptyLine(scanner)
  439. if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
  440. return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
  441. }
  442. field := strings.SplitN(firstLine, ": ", 2)
  443. v, err := strconv.ParseUint(field[1], 0, 32)
  444. if err != nil {
  445. return nil, err
  446. }
  447. firstcpu := CPUInfo{Processor: uint(v)}
  448. cpuinfo := []CPUInfo{firstcpu}
  449. i := 0
  450. for scanner.Scan() {
  451. line := scanner.Text()
  452. if !strings.Contains(line, ":") {
  453. continue
  454. }
  455. field := strings.SplitN(line, ": ", 2)
  456. switch strings.TrimSpace(field[0]) {
  457. case "processor":
  458. v, err := strconv.ParseUint(field[1], 0, 32)
  459. if err != nil {
  460. return nil, err
  461. }
  462. i = int(v)
  463. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  464. cpuinfo[i].Processor = uint(v)
  465. case "hart":
  466. cpuinfo[i].CoreID = field[1]
  467. case "isa":
  468. cpuinfo[i].ModelName = field[1]
  469. }
  470. }
  471. return cpuinfo, nil
  472. }
  473. func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode
  474. return nil, errors.New("not implemented")
  475. }
  476. // firstNonEmptyLine advances the scanner to the first non-empty line
  477. // and returns the contents of that line.
  478. func firstNonEmptyLine(scanner *bufio.Scanner) string {
  479. for scanner.Scan() {
  480. line := scanner.Text()
  481. if strings.TrimSpace(line) != "" {
  482. return line
  483. }
  484. }
  485. return ""
  486. }