You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

482 lines
12 KiB

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