cpuinfo.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. // +build linux
  14. package procfs
  15. import (
  16. "bufio"
  17. "bytes"
  18. "errors"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "github.com/prometheus/procfs/internal/util"
  23. )
  24. // CPUInfo contains general information about a system CPU found in /proc/cpuinfo
  25. type CPUInfo struct {
  26. Processor uint
  27. VendorID string
  28. CPUFamily string
  29. Model string
  30. ModelName string
  31. Stepping string
  32. Microcode string
  33. CPUMHz float64
  34. CacheSize string
  35. PhysicalID string
  36. Siblings uint
  37. CoreID string
  38. CPUCores uint
  39. APICID string
  40. InitialAPICID string
  41. FPU string
  42. FPUException string
  43. CPUIDLevel uint
  44. WP string
  45. Flags []string
  46. Bugs []string
  47. BogoMips float64
  48. CLFlushSize uint
  49. CacheAlignment uint
  50. AddressSizes string
  51. PowerManagement string
  52. }
  53. var (
  54. cpuinfoClockRegexp = regexp.MustCompile(`([\d.]+)`)
  55. cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`)
  56. )
  57. // CPUInfo returns information about current system CPUs.
  58. // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
  59. func (fs FS) CPUInfo() ([]CPUInfo, error) {
  60. data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
  61. if err != nil {
  62. return nil, err
  63. }
  64. return parseCPUInfo(data)
  65. }
  66. func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
  67. scanner := bufio.NewScanner(bytes.NewReader(info))
  68. // find the first "processor" line
  69. firstLine := firstNonEmptyLine(scanner)
  70. if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
  71. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  72. }
  73. field := strings.SplitN(firstLine, ": ", 2)
  74. v, err := strconv.ParseUint(field[1], 0, 32)
  75. if err != nil {
  76. return nil, err
  77. }
  78. firstcpu := CPUInfo{Processor: uint(v)}
  79. cpuinfo := []CPUInfo{firstcpu}
  80. i := 0
  81. for scanner.Scan() {
  82. line := scanner.Text()
  83. if !strings.Contains(line, ":") {
  84. continue
  85. }
  86. field := strings.SplitN(line, ": ", 2)
  87. switch strings.TrimSpace(field[0]) {
  88. case "processor":
  89. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  90. i++
  91. v, err := strconv.ParseUint(field[1], 0, 32)
  92. if err != nil {
  93. return nil, err
  94. }
  95. cpuinfo[i].Processor = uint(v)
  96. case "vendor", "vendor_id":
  97. cpuinfo[i].VendorID = field[1]
  98. case "cpu family":
  99. cpuinfo[i].CPUFamily = field[1]
  100. case "model":
  101. cpuinfo[i].Model = field[1]
  102. case "model name":
  103. cpuinfo[i].ModelName = field[1]
  104. case "stepping":
  105. cpuinfo[i].Stepping = field[1]
  106. case "microcode":
  107. cpuinfo[i].Microcode = field[1]
  108. case "cpu MHz":
  109. v, err := strconv.ParseFloat(field[1], 64)
  110. if err != nil {
  111. return nil, err
  112. }
  113. cpuinfo[i].CPUMHz = v
  114. case "cache size":
  115. cpuinfo[i].CacheSize = field[1]
  116. case "physical id":
  117. cpuinfo[i].PhysicalID = field[1]
  118. case "siblings":
  119. v, err := strconv.ParseUint(field[1], 0, 32)
  120. if err != nil {
  121. return nil, err
  122. }
  123. cpuinfo[i].Siblings = uint(v)
  124. case "core id":
  125. cpuinfo[i].CoreID = field[1]
  126. case "cpu cores":
  127. v, err := strconv.ParseUint(field[1], 0, 32)
  128. if err != nil {
  129. return nil, err
  130. }
  131. cpuinfo[i].CPUCores = uint(v)
  132. case "apicid":
  133. cpuinfo[i].APICID = field[1]
  134. case "initial apicid":
  135. cpuinfo[i].InitialAPICID = field[1]
  136. case "fpu":
  137. cpuinfo[i].FPU = field[1]
  138. case "fpu_exception":
  139. cpuinfo[i].FPUException = field[1]
  140. case "cpuid level":
  141. v, err := strconv.ParseUint(field[1], 0, 32)
  142. if err != nil {
  143. return nil, err
  144. }
  145. cpuinfo[i].CPUIDLevel = uint(v)
  146. case "wp":
  147. cpuinfo[i].WP = field[1]
  148. case "flags":
  149. cpuinfo[i].Flags = strings.Fields(field[1])
  150. case "bugs":
  151. cpuinfo[i].Bugs = strings.Fields(field[1])
  152. case "bogomips":
  153. v, err := strconv.ParseFloat(field[1], 64)
  154. if err != nil {
  155. return nil, err
  156. }
  157. cpuinfo[i].BogoMips = v
  158. case "clflush size":
  159. v, err := strconv.ParseUint(field[1], 0, 32)
  160. if err != nil {
  161. return nil, err
  162. }
  163. cpuinfo[i].CLFlushSize = uint(v)
  164. case "cache_alignment":
  165. v, err := strconv.ParseUint(field[1], 0, 32)
  166. if err != nil {
  167. return nil, err
  168. }
  169. cpuinfo[i].CacheAlignment = uint(v)
  170. case "address sizes":
  171. cpuinfo[i].AddressSizes = field[1]
  172. case "power management":
  173. cpuinfo[i].PowerManagement = field[1]
  174. }
  175. }
  176. return cpuinfo, nil
  177. }
  178. func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
  179. scanner := bufio.NewScanner(bytes.NewReader(info))
  180. firstLine := firstNonEmptyLine(scanner)
  181. match, _ := regexp.MatchString("^[Pp]rocessor", firstLine)
  182. if !match || !strings.Contains(firstLine, ":") {
  183. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  184. }
  185. field := strings.SplitN(firstLine, ": ", 2)
  186. cpuinfo := []CPUInfo{}
  187. featuresLine := ""
  188. commonCPUInfo := CPUInfo{}
  189. i := 0
  190. if strings.TrimSpace(field[0]) == "Processor" {
  191. commonCPUInfo = CPUInfo{ModelName: field[1]}
  192. i = -1
  193. } else {
  194. v, err := strconv.ParseUint(field[1], 0, 32)
  195. if err != nil {
  196. return nil, err
  197. }
  198. firstcpu := CPUInfo{Processor: uint(v)}
  199. cpuinfo = []CPUInfo{firstcpu}
  200. }
  201. for scanner.Scan() {
  202. line := scanner.Text()
  203. if !strings.Contains(line, ":") {
  204. continue
  205. }
  206. field := strings.SplitN(line, ": ", 2)
  207. switch strings.TrimSpace(field[0]) {
  208. case "processor":
  209. cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
  210. i++
  211. v, err := strconv.ParseUint(field[1], 0, 32)
  212. if err != nil {
  213. return nil, err
  214. }
  215. cpuinfo[i].Processor = uint(v)
  216. case "BogoMIPS":
  217. if i == -1 {
  218. cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor
  219. i++
  220. cpuinfo[i].Processor = 0
  221. }
  222. v, err := strconv.ParseFloat(field[1], 64)
  223. if err != nil {
  224. return nil, err
  225. }
  226. cpuinfo[i].BogoMips = v
  227. case "Features":
  228. featuresLine = line
  229. case "model name":
  230. cpuinfo[i].ModelName = field[1]
  231. }
  232. }
  233. fields := strings.SplitN(featuresLine, ": ", 2)
  234. for i := range cpuinfo {
  235. cpuinfo[i].Flags = strings.Fields(fields[1])
  236. }
  237. return cpuinfo, nil
  238. }
  239. func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
  240. scanner := bufio.NewScanner(bytes.NewReader(info))
  241. firstLine := firstNonEmptyLine(scanner)
  242. if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
  243. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  244. }
  245. field := strings.SplitN(firstLine, ": ", 2)
  246. cpuinfo := []CPUInfo{}
  247. commonCPUInfo := CPUInfo{VendorID: field[1]}
  248. for scanner.Scan() {
  249. line := scanner.Text()
  250. if !strings.Contains(line, ":") {
  251. continue
  252. }
  253. field := strings.SplitN(line, ": ", 2)
  254. switch strings.TrimSpace(field[0]) {
  255. case "bogomips per cpu":
  256. v, err := strconv.ParseFloat(field[1], 64)
  257. if err != nil {
  258. return nil, err
  259. }
  260. commonCPUInfo.BogoMips = v
  261. case "features":
  262. commonCPUInfo.Flags = strings.Fields(field[1])
  263. }
  264. if strings.HasPrefix(line, "processor") {
  265. match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
  266. if len(match) < 2 {
  267. return nil, errors.New("Invalid line found in cpuinfo: " + line)
  268. }
  269. cpu := commonCPUInfo
  270. v, err := strconv.ParseUint(match[1], 0, 32)
  271. if err != nil {
  272. return nil, err
  273. }
  274. cpu.Processor = uint(v)
  275. cpuinfo = append(cpuinfo, cpu)
  276. }
  277. if strings.HasPrefix(line, "cpu number") {
  278. break
  279. }
  280. }
  281. i := 0
  282. for scanner.Scan() {
  283. line := scanner.Text()
  284. if !strings.Contains(line, ":") {
  285. continue
  286. }
  287. field := strings.SplitN(line, ": ", 2)
  288. switch strings.TrimSpace(field[0]) {
  289. case "cpu number":
  290. i++
  291. case "cpu MHz dynamic":
  292. clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
  293. v, err := strconv.ParseFloat(clock, 64)
  294. if err != nil {
  295. return nil, err
  296. }
  297. cpuinfo[i].CPUMHz = v
  298. }
  299. }
  300. return cpuinfo, nil
  301. }
  302. func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
  303. scanner := bufio.NewScanner(bytes.NewReader(info))
  304. // find the first "processor" line
  305. firstLine := firstNonEmptyLine(scanner)
  306. if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
  307. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  308. }
  309. field := strings.SplitN(firstLine, ": ", 2)
  310. cpuinfo := []CPUInfo{}
  311. systemType := field[1]
  312. i := 0
  313. for scanner.Scan() {
  314. line := scanner.Text()
  315. if !strings.Contains(line, ":") {
  316. continue
  317. }
  318. field := strings.SplitN(line, ": ", 2)
  319. switch strings.TrimSpace(field[0]) {
  320. case "processor":
  321. v, err := strconv.ParseUint(field[1], 0, 32)
  322. if err != nil {
  323. return nil, err
  324. }
  325. i = int(v)
  326. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  327. cpuinfo[i].Processor = uint(v)
  328. cpuinfo[i].VendorID = systemType
  329. case "cpu model":
  330. cpuinfo[i].ModelName = field[1]
  331. case "BogoMIPS":
  332. v, err := strconv.ParseFloat(field[1], 64)
  333. if err != nil {
  334. return nil, err
  335. }
  336. cpuinfo[i].BogoMips = v
  337. }
  338. }
  339. return cpuinfo, nil
  340. }
  341. func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
  342. scanner := bufio.NewScanner(bytes.NewReader(info))
  343. firstLine := firstNonEmptyLine(scanner)
  344. if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
  345. return nil, errors.New("invalid cpuinfo file: " + firstLine)
  346. }
  347. field := strings.SplitN(firstLine, ": ", 2)
  348. v, err := strconv.ParseUint(field[1], 0, 32)
  349. if err != nil {
  350. return nil, err
  351. }
  352. firstcpu := CPUInfo{Processor: uint(v)}
  353. cpuinfo := []CPUInfo{firstcpu}
  354. i := 0
  355. for scanner.Scan() {
  356. line := scanner.Text()
  357. if !strings.Contains(line, ":") {
  358. continue
  359. }
  360. field := strings.SplitN(line, ": ", 2)
  361. switch strings.TrimSpace(field[0]) {
  362. case "processor":
  363. cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
  364. i++
  365. v, err := strconv.ParseUint(field[1], 0, 32)
  366. if err != nil {
  367. return nil, err
  368. }
  369. cpuinfo[i].Processor = uint(v)
  370. case "cpu":
  371. cpuinfo[i].VendorID = field[1]
  372. case "clock":
  373. clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
  374. v, err := strconv.ParseFloat(clock, 64)
  375. if err != nil {
  376. return nil, err
  377. }
  378. cpuinfo[i].CPUMHz = v
  379. }
  380. }
  381. return cpuinfo, nil
  382. }
  383. // firstNonEmptyLine advances the scanner to the first non-empty line
  384. // and returns the contents of that line
  385. func firstNonEmptyLine(scanner *bufio.Scanner) string {
  386. for scanner.Scan() {
  387. line := scanner.Text()
  388. if strings.TrimSpace(line) != "" {
  389. return line
  390. }
  391. }
  392. return ""
  393. }