diff --git a/pkg/cpu/cpu_linux.go b/pkg/cpu/cpu_linux.go index 44e4ced7..d4b048d7 100644 --- a/pkg/cpu/cpu_linux.go +++ b/pkg/cpu/cpu_linux.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strconv" "strings" @@ -19,6 +20,10 @@ import ( "github.com/jaypipes/ghw/pkg/util" ) +var ( + regexForCpulCore = regexp.MustCompile("^cpu([0-9]+)$") +) + func (i *Info) load() error { i.Processors = processorsGet(i.ctx) var totCores uint32 @@ -32,6 +37,24 @@ func (i *Info) load() error { return nil } +func ProcByID(procs []*Processor, id int) *Processor { + for pid := range procs { + if procs[pid].ID == id { + return procs[pid] + } + } + return nil +} + +func CoreByID(cores []*ProcessorCore, id int) *ProcessorCore { + for cid := range cores { + if cores[cid].Index == id { + return cores[cid] + } + } + return nil +} + func processorsGet(ctx *context.Context) []*Processor { procs := make([]*Processor, 0) paths := linuxpath.New(ctx) @@ -46,6 +69,7 @@ func processorsGet(ctx *context.Context) []*Processor { procAttrs := make([]map[string]string, 0) curProcAttrs := make(map[string]string) + // Parse /proc/cpuinfo scanner := bufio.NewScanner(r) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) @@ -64,85 +88,64 @@ func processorsGet(ctx *context.Context) []*Processor { curProcAttrs[key] = value } - // Build a set of physical processor IDs which represent the physical - // package of the CPU - setPhysicalIDs := make(map[int]bool) - for _, attrs := range procAttrs { - pid, err := strconv.Atoi(attrs["physical id"]) - if err != nil { - continue - } - setPhysicalIDs[pid] = true + // Iterate on /sys/devices/system/cpu/cpuN, not on /proc/cpuinfo + Entries, err := ioutil.ReadDir(paths.SysDevicesSystemCPU) + if err != nil { + return nil } - - for pid := range setPhysicalIDs { - p := &Processor{ - ID: pid, - } - // The indexes into the array of attribute maps for each logical - // processor within the physical processor - lps := make([]int, 0) - for x := range procAttrs { - lppid, err := strconv.Atoi(procAttrs[x]["physical id"]) - if err != nil { - continue - } - if pid == lppid { - lps = append(lps, x) - } - } - first := procAttrs[lps[0]] - p.Model = first["model name"] - p.Vendor = first["vendor_id"] - numCores, err := strconv.Atoi(first["cpu cores"]) - if err != nil { + for _, lcore := range Entries { + matches := regexForCpulCore.FindStringSubmatch(lcore.Name()) + if len(matches) < 2 { continue } - p.NumCores = uint32(numCores) - numThreads, err := strconv.Atoi(first["siblings"]) - if err != nil { + + lcoreID, error := strconv.Atoi(matches[1]) + if error != nil { continue } - p.NumThreads = uint32(numThreads) - - // The flags field is a space-separated list of CPU capabilities - p.Capabilities = strings.Split(first["flags"], " ") - cores := make([]*ProcessorCore, 0) - for _, lpidx := range lps { - lpid, err := strconv.Atoi(procAttrs[lpidx]["processor"]) - if err != nil { - continue + // Fetch CPU ID + physIdPath := filepath.Join(paths.SysDevicesSystemCPU, fmt.Sprintf("cpu%d", lcoreID), "topology", "physical_package_id") + cpuID := util.SafeIntFromFile(ctx, physIdPath) + + proc := ProcByID(procs, cpuID) + if proc == nil { + proc = &Processor{ID: cpuID} + // Assumes /proc/cpuinfo is in order of logical cpu id, then + // procAttrs[lcoreID] describes logical cpu `lcoreID`. + // Once got a more robust way of fetching the following info, + // can we drop /proc/cpuinfo. + if len(procAttrs[lcoreID]["flags"]) != 0 { // x86 + proc.Capabilities = strings.Split(procAttrs[lcoreID]["flags"], " ") + } else if len(procAttrs[lcoreID]["Features"]) != 0 { // ARM64 + proc.Capabilities = strings.Split(procAttrs[lcoreID]["Features"], " ") } - coreID, err := strconv.Atoi(procAttrs[lpidx]["core id"]) - if err != nil { - continue + if len(procAttrs[lcoreID]["model name"]) != 0 { + proc.Model = procAttrs[lcoreID]["model name"] + } else if len(procAttrs[lcoreID]["uarch"]) != 0 { // SiFive + proc.Model = procAttrs[lcoreID]["uarch"] } - var core *ProcessorCore - for _, c := range cores { - if c.ID == coreID { - c.LogicalProcessors = append( - c.LogicalProcessors, - lpid, - ) - c.NumThreads = uint32(len(c.LogicalProcessors)) - core = c - } - } - if core == nil { - coreLps := make([]int, 1) - coreLps[0] = lpid - core = &ProcessorCore{ - ID: coreID, - Index: len(cores), - NumThreads: 1, - LogicalProcessors: coreLps, - } - cores = append(cores, core) + if len(procAttrs[lcoreID]["vendor_id"]) != 0 { + proc.Vendor = procAttrs[lcoreID]["vendor_id"] + } else if len(procAttrs[lcoreID]["isa"]) != 0 { // RISCV64 + proc.Vendor = procAttrs[lcoreID]["isa"] } + procs = append(procs, proc) + } + + // Fetch Core ID + coreIdPath := filepath.Join(paths.SysDevicesSystemCPU, fmt.Sprintf("cpu%d", lcoreID), "topology", "core_id") + coreID := util.SafeIntFromFile(ctx, coreIdPath) + core := CoreByID(proc.Cores, coreID) + if core == nil { + core = &ProcessorCore{Index: coreID, NumThreads: 1} + proc.Cores = append(proc.Cores, core) + proc.NumCores += 1 + } else { + core.NumThreads += 1 } - p.Cores = cores - procs = append(procs, p) + proc.NumThreads += 1 + core.LogicalProcessors = append(core.LogicalProcessors, lcoreID) } return procs } diff --git a/pkg/cpu/cpu_linux_test.go b/pkg/cpu/cpu_linux_test.go new file mode 100644 index 00000000..3c334b57 --- /dev/null +++ b/pkg/cpu/cpu_linux_test.go @@ -0,0 +1,73 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package cpu_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/jaypipes/ghw/pkg/cpu" + "github.com/jaypipes/ghw/pkg/option" + "github.com/jaypipes/ghw/testdata" +) + +// nolint: gocyclo +func TestArmCPU(t *testing.T) { + if _, ok := os.LookupEnv("GHW_TESTING_SKIP_CPU"); ok { + t.Skip("Skipping CPU tests.") + } + + testdataPath, err := testdata.SnapshotsDirectory() + if err != nil { + t.Fatalf("Expected nil err, but got %v", err) + } + + multiNumaSnapshot := filepath.Join(testdataPath, "linux-arm64-c288e0776090cd558ef793b2a4e61939.tar.gz") + + info, err := cpu.New(option.WithSnapshot(option.SnapshotOptions{ + Path: multiNumaSnapshot, + })) + + if err != nil { + t.Fatalf("Expected nil err, but got %v", err) + } + if info == nil { + t.Fatalf("Expected non-nil CPUInfo, but got nil") + } + + if len(info.Processors) == 0 { + t.Fatalf("Expected >0 processors but got 0.") + } + + for _, p := range info.Processors { + if p.NumCores == 0 { + t.Fatalf("Expected >0 cores but got 0.") + } + if p.NumThreads == 0 { + t.Fatalf("Expected >0 threads but got 0.") + } + if len(p.Capabilities) == 0 { + t.Fatalf("Expected >0 capabilities but got 0.") + } + if !p.HasCapability(p.Capabilities[0]) { + t.Fatalf("Expected p to have capability %s, but did not.", + p.Capabilities[0]) + } + if len(p.Cores) == 0 { + t.Fatalf("Expected >0 cores in processor, but got 0.") + } + for _, c := range p.Cores { + if c.NumThreads == 0 { + t.Fatalf("Expected >0 threads but got 0.") + } + if len(c.LogicalProcessors) == 0 { + t.Fatalf("Expected >0 logical processors but got 0.") + } + } + } +} diff --git a/pkg/linuxpath/path_linux.go b/pkg/linuxpath/path_linux.go index c5967d61..bbe81b64 100644 --- a/pkg/linuxpath/path_linux.go +++ b/pkg/linuxpath/path_linux.go @@ -64,6 +64,7 @@ type Paths struct { SysBlock string SysDevicesSystemNode string SysDevicesSystemMemory string + SysDevicesSystemCPU string SysBusPciDevices string SysClassDRM string SysClassDMI string @@ -84,6 +85,7 @@ func New(ctx *context.Context) *Paths { SysBlock: filepath.Join(ctx.Chroot, roots.Sys, "block"), SysDevicesSystemNode: filepath.Join(ctx.Chroot, roots.Sys, "devices", "system", "node"), SysDevicesSystemMemory: filepath.Join(ctx.Chroot, roots.Sys, "devices", "system", "memory"), + SysDevicesSystemCPU: filepath.Join(ctx.Chroot, roots.Sys, "devices", "system", "cpu"), SysBusPciDevices: filepath.Join(ctx.Chroot, roots.Sys, "bus", "pci", "devices"), SysClassDRM: filepath.Join(ctx.Chroot, roots.Sys, "class", "drm"), SysClassDMI: filepath.Join(ctx.Chroot, roots.Sys, "class", "dmi"), diff --git a/testdata/snapshots/linux-arm64-c288e0776090cd558ef793b2a4e61939.tar.gz b/testdata/snapshots/linux-arm64-c288e0776090cd558ef793b2a4e61939.tar.gz new file mode 100644 index 00000000..8d76a865 Binary files /dev/null and b/testdata/snapshots/linux-arm64-c288e0776090cd558ef793b2a4e61939.tar.gz differ