Files
cloudpods/vendor/github.com/jaypipes/ghw/pkg/cpu/cpu_linux.go
2023-04-17 23:35:48 +08:00

224 lines
6.0 KiB
Go

// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//
package cpu
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/linuxpath"
"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
var totThreads uint32
for _, p := range i.Processors {
totCores += p.NumCores
totThreads += p.NumThreads
}
i.TotalCores = totCores
i.TotalThreads = totThreads
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)
r, err := os.Open(paths.ProcCpuinfo)
if err != nil {
return nil
}
defer util.SafeClose(r)
// An array of maps of attributes describing the logical 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())
if line == "" {
// Output of /proc/cpuinfo has a blank newline to separate logical
// processors, so here we collect up all the attributes we've
// collected for this logical processor block
procAttrs = append(procAttrs, curProcAttrs)
// Reset the current set of processor attributes...
curProcAttrs = make(map[string]string)
continue
}
parts := strings.Split(line, ":")
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
curProcAttrs[key] = value
}
// Iterate on /sys/devices/system/cpu/cpuN, not on /proc/cpuinfo
Entries, err := ioutil.ReadDir(paths.SysDevicesSystemCPU)
if err != nil {
return nil
}
for _, lcore := range Entries {
matches := regexForCpulCore.FindStringSubmatch(lcore.Name())
if len(matches) < 2 {
continue
}
lcoreID, error := strconv.Atoi(matches[1])
if error != 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"], " ")
}
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"]
}
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
}
proc.NumThreads += 1
core.LogicalProcessors = append(core.LogicalProcessors, lcoreID)
}
return procs
}
func CoresForNode(ctx *context.Context, nodeID int) ([]*ProcessorCore, error) {
// The /sys/devices/system/node/nodeX directory contains a subdirectory
// called 'cpuX' for each logical processor assigned to the node. Each of
// those subdirectories contains a topology subdirectory which has a
// core_id file that indicates the 0-based identifier of the physical core
// the logical processor (hardware thread) is on.
paths := linuxpath.New(ctx)
path := filepath.Join(
paths.SysDevicesSystemNode,
fmt.Sprintf("node%d", nodeID),
)
cores := make([]*ProcessorCore, 0)
findCoreByID := func(coreID int) *ProcessorCore {
for _, c := range cores {
if c.ID == coreID {
return c
}
}
c := &ProcessorCore{
ID: coreID,
Index: len(cores),
LogicalProcessors: make([]int, 0),
}
cores = append(cores, c)
return c
}
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
for _, file := range files {
filename := file.Name()
if !strings.HasPrefix(filename, "cpu") {
continue
}
if filename == "cpumap" || filename == "cpulist" {
// There are two files in the node directory that start with 'cpu'
// but are not subdirectories ('cpulist' and 'cpumap'). Ignore
// these files.
continue
}
// Grab the logical processor ID by cutting the integer from the
// /sys/devices/system/node/nodeX/cpuX filename
cpuPath := filepath.Join(path, filename)
procID, err := strconv.Atoi(filename[3:])
if err != nil {
_, _ = fmt.Fprintf(
os.Stderr,
"failed to determine procID from %s. Expected integer after 3rd char.",
filename,
)
continue
}
coreIDPath := filepath.Join(cpuPath, "topology", "core_id")
coreID := util.SafeIntFromFile(ctx, coreIDPath)
core := findCoreByID(coreID)
core.LogicalProcessors = append(
core.LogicalProcessors,
procID,
)
}
for _, c := range cores {
c.NumThreads = uint32(len(c.LogicalProcessors))
}
return cores, nil
}