cpu_linux.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // Use and distribution licensed under the Apache license version 2.
  2. //
  3. // See the COPYING file in the root project directory for full text.
  4. //
  5. package cpu
  6. import (
  7. "bufio"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "github.com/jaypipes/ghw/pkg/context"
  16. "github.com/jaypipes/ghw/pkg/linuxpath"
  17. "github.com/jaypipes/ghw/pkg/util"
  18. )
  19. var (
  20. regexForCpulCore = regexp.MustCompile("^cpu([0-9]+)$")
  21. )
  22. func (i *Info) load() error {
  23. i.Processors = processorsGet(i.ctx)
  24. var totCores uint32
  25. var totThreads uint32
  26. for _, p := range i.Processors {
  27. totCores += p.NumCores
  28. totThreads += p.NumThreads
  29. }
  30. i.TotalCores = totCores
  31. i.TotalThreads = totThreads
  32. return nil
  33. }
  34. func ProcByID(procs []*Processor, id int) *Processor {
  35. for pid := range procs {
  36. if procs[pid].ID == id {
  37. return procs[pid]
  38. }
  39. }
  40. return nil
  41. }
  42. func CoreByID(cores []*ProcessorCore, id int) *ProcessorCore {
  43. for cid := range cores {
  44. if cores[cid].Index == id {
  45. return cores[cid]
  46. }
  47. }
  48. return nil
  49. }
  50. func processorsGet(ctx *context.Context) []*Processor {
  51. procs := make([]*Processor, 0)
  52. paths := linuxpath.New(ctx)
  53. r, err := os.Open(paths.ProcCpuinfo)
  54. if err != nil {
  55. return nil
  56. }
  57. defer util.SafeClose(r)
  58. // An array of maps of attributes describing the logical processor
  59. procAttrs := make([]map[string]string, 0)
  60. curProcAttrs := make(map[string]string)
  61. // Parse /proc/cpuinfo
  62. scanner := bufio.NewScanner(r)
  63. for scanner.Scan() {
  64. line := strings.TrimSpace(scanner.Text())
  65. if line == "" {
  66. // Output of /proc/cpuinfo has a blank newline to separate logical
  67. // processors, so here we collect up all the attributes we've
  68. // collected for this logical processor block
  69. procAttrs = append(procAttrs, curProcAttrs)
  70. // Reset the current set of processor attributes...
  71. curProcAttrs = make(map[string]string)
  72. continue
  73. }
  74. parts := strings.Split(line, ":")
  75. key := strings.TrimSpace(parts[0])
  76. value := strings.TrimSpace(parts[1])
  77. curProcAttrs[key] = value
  78. }
  79. // Iterate on /sys/devices/system/cpu/cpuN, not on /proc/cpuinfo
  80. Entries, err := ioutil.ReadDir(paths.SysDevicesSystemCPU)
  81. if err != nil {
  82. return nil
  83. }
  84. for _, lcore := range Entries {
  85. matches := regexForCpulCore.FindStringSubmatch(lcore.Name())
  86. if len(matches) < 2 {
  87. continue
  88. }
  89. lcoreID, error := strconv.Atoi(matches[1])
  90. if error != nil {
  91. continue
  92. }
  93. // Fetch CPU ID
  94. physIdPath := filepath.Join(paths.SysDevicesSystemCPU, fmt.Sprintf("cpu%d", lcoreID), "topology", "physical_package_id")
  95. cpuID := util.SafeIntFromFile(ctx, physIdPath)
  96. proc := ProcByID(procs, cpuID)
  97. if proc == nil {
  98. proc = &Processor{ID: cpuID}
  99. // Assumes /proc/cpuinfo is in order of logical cpu id, then
  100. // procAttrs[lcoreID] describes logical cpu `lcoreID`.
  101. // Once got a more robust way of fetching the following info,
  102. // can we drop /proc/cpuinfo.
  103. if len(procAttrs[lcoreID]["flags"]) != 0 { // x86
  104. proc.Capabilities = strings.Split(procAttrs[lcoreID]["flags"], " ")
  105. } else if len(procAttrs[lcoreID]["Features"]) != 0 { // ARM64
  106. proc.Capabilities = strings.Split(procAttrs[lcoreID]["Features"], " ")
  107. }
  108. if len(procAttrs[lcoreID]["model name"]) != 0 {
  109. proc.Model = procAttrs[lcoreID]["model name"]
  110. } else if len(procAttrs[lcoreID]["uarch"]) != 0 { // SiFive
  111. proc.Model = procAttrs[lcoreID]["uarch"]
  112. }
  113. if len(procAttrs[lcoreID]["vendor_id"]) != 0 {
  114. proc.Vendor = procAttrs[lcoreID]["vendor_id"]
  115. } else if len(procAttrs[lcoreID]["isa"]) != 0 { // RISCV64
  116. proc.Vendor = procAttrs[lcoreID]["isa"]
  117. }
  118. procs = append(procs, proc)
  119. }
  120. // Fetch Core ID
  121. coreIdPath := filepath.Join(paths.SysDevicesSystemCPU, fmt.Sprintf("cpu%d", lcoreID), "topology", "core_id")
  122. coreID := util.SafeIntFromFile(ctx, coreIdPath)
  123. core := CoreByID(proc.Cores, coreID)
  124. if core == nil {
  125. core = &ProcessorCore{Index: coreID, NumThreads: 1}
  126. proc.Cores = append(proc.Cores, core)
  127. proc.NumCores += 1
  128. } else {
  129. core.NumThreads += 1
  130. }
  131. proc.NumThreads += 1
  132. core.LogicalProcessors = append(core.LogicalProcessors, lcoreID)
  133. }
  134. return procs
  135. }
  136. func CoresForNode(ctx *context.Context, nodeID int) ([]*ProcessorCore, error) {
  137. // The /sys/devices/system/node/nodeX directory contains a subdirectory
  138. // called 'cpuX' for each logical processor assigned to the node. Each of
  139. // those subdirectories contains a topology subdirectory which has a
  140. // core_id file that indicates the 0-based identifier of the physical core
  141. // the logical processor (hardware thread) is on.
  142. paths := linuxpath.New(ctx)
  143. path := filepath.Join(
  144. paths.SysDevicesSystemNode,
  145. fmt.Sprintf("node%d", nodeID),
  146. )
  147. cores := make([]*ProcessorCore, 0)
  148. findCoreByID := func(coreID int) *ProcessorCore {
  149. for _, c := range cores {
  150. if c.ID == coreID {
  151. return c
  152. }
  153. }
  154. c := &ProcessorCore{
  155. ID: coreID,
  156. Index: len(cores),
  157. LogicalProcessors: make([]int, 0),
  158. }
  159. cores = append(cores, c)
  160. return c
  161. }
  162. files, err := ioutil.ReadDir(path)
  163. if err != nil {
  164. return nil, err
  165. }
  166. for _, file := range files {
  167. filename := file.Name()
  168. if !strings.HasPrefix(filename, "cpu") {
  169. continue
  170. }
  171. if filename == "cpumap" || filename == "cpulist" {
  172. // There are two files in the node directory that start with 'cpu'
  173. // but are not subdirectories ('cpulist' and 'cpumap'). Ignore
  174. // these files.
  175. continue
  176. }
  177. // Grab the logical processor ID by cutting the integer from the
  178. // /sys/devices/system/node/nodeX/cpuX filename
  179. cpuPath := filepath.Join(path, filename)
  180. procID, err := strconv.Atoi(filename[3:])
  181. if err != nil {
  182. _, _ = fmt.Fprintf(
  183. os.Stderr,
  184. "failed to determine procID from %s. Expected integer after 3rd char.",
  185. filename,
  186. )
  187. continue
  188. }
  189. coreIDPath := filepath.Join(cpuPath, "topology", "core_id")
  190. coreID := util.SafeIntFromFile(ctx, coreIDPath)
  191. core := findCoreByID(coreID)
  192. core.LogicalProcessors = append(
  193. core.LogicalProcessors,
  194. procID,
  195. )
  196. }
  197. for _, c := range cores {
  198. c.NumThreads = uint32(len(c.LogicalProcessors))
  199. }
  200. return cores, nil
  201. }