cc-metric-collector/pkg/ccTopology/ccTopology.go
2023-09-06 11:35:45 +02:00

456 lines
10 KiB
Go

package ccTopology
import (
"bufio"
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
cclogger "github.com/ClusterCockpit/cc-metric-collector/pkg/ccLogger"
"golang.org/x/exp/slices"
)
const (
SYSFS_NUMABASE = `/sys/devices/system/node`
SYSFS_CPUBASE = `/sys/devices/system/cpu`
PROCFS_CPUINFO = `/proc/cpuinfo`
)
// fileToInt reads an integer value from a file
// In case of an error -1 is returned
// Used internally for sysfs file reads
func fileToInt(path string) int {
buffer, err := os.ReadFile(path)
if err != nil {
log.Print(err)
cclogger.ComponentError("ccTopology", "Reading", path, ":", err.Error())
return -1
}
sbuffer := strings.TrimSpace(string(buffer))
id, err := strconv.ParseInt(sbuffer, 10, 32)
if err != nil {
cclogger.ComponentError("ccTopology", "Parsing", path, ":", sbuffer, err.Error())
return -1
}
return int(id)
}
// SocketList gets the list of CPU socket IDs
func SocketList() []int {
file, err := os.Open(string(PROCFS_CPUINFO))
if err != nil {
log.Print(err)
return nil
}
defer file.Close()
packs := make([]int, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "physical id") {
lv := strings.Fields(line)
id, err := strconv.ParseInt(lv[3], 10, 32)
if err != nil {
log.Print(err)
return nil
}
if found := slices.Contains(packs, int(id)); !found {
packs = append(packs, int(id))
}
}
}
return packs
}
// HwthreadList gets the list of hardware thread IDs in the order of listing in /proc/cpuinfo
func HwthreadList() []int {
file, err := os.Open(string(PROCFS_CPUINFO))
if err != nil {
log.Print(err)
return nil
}
defer file.Close()
cpuList := make([]int, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "processor") {
lv := strings.Fields(line)
id, err := strconv.ParseInt(lv[2], 10, 32)
if err != nil {
log.Print(err)
return nil
}
if found := slices.Contains(cpuList, int(id)); !found {
cpuList = append(cpuList, int(id))
}
}
}
return cpuList
}
// Get list of hardware thread IDs in the order of listing in /proc/cpuinfo
// Deprecated! Use HwthreadList()
func CpuList() []int {
return HwthreadList()
}
// CoreList gets the list of CPU core IDs in the order of listing in /proc/cpuinfo
func CoreList() []int {
file, err := os.Open(string(PROCFS_CPUINFO))
if err != nil {
log.Print(err)
return nil
}
defer file.Close()
coreList := make([]int, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "core id") {
lv := strings.Fields(line)
id, err := strconv.ParseInt(lv[3], 10, 32)
if err != nil {
log.Print(err)
return nil
}
if found := slices.Contains(coreList, int(id)); !found {
coreList = append(coreList, int(id))
}
}
}
return coreList
}
// Get list of NUMA node IDs
func NumaNodeList() []int {
numaList := make([]int, 0)
globPath := filepath.Join(string(SYSFS_NUMABASE), "node*")
regexPath := filepath.Join(string(SYSFS_NUMABASE), "node(\\d+)")
regex := regexp.MustCompile(regexPath)
files, err := filepath.Glob(globPath)
if err != nil {
cclogger.ComponentError("CCTopology", "NumaNodeList", err.Error())
}
for _, f := range files {
if !regex.MatchString(f) {
continue
}
finfo, err := os.Lstat(f)
if err != nil {
continue
}
if !finfo.IsDir() {
continue
}
matches := regex.FindStringSubmatch(f)
if len(matches) == 2 {
id, err := strconv.Atoi(matches[1])
if err == nil {
if found := slices.Contains(numaList, id); !found {
numaList = append(numaList, id)
}
}
}
}
return numaList
}
// DieList gets the list of CPU die IDs
func DieList() []int {
cpuList := HwthreadList()
dieList := make([]int, 0)
for _, c := range cpuList {
diePath := filepath.Join(
string(SYSFS_CPUBASE),
fmt.Sprintf("cpu%d", c),
"topology/die_id")
dieID := fileToInt(diePath)
if dieID > 0 {
if found := slices.Contains(dieList, int(dieID)); !found {
dieList = append(dieList, int(dieID))
}
}
}
if len(dieList) > 0 {
return dieList
}
return SocketList()
}
// GetTypeList gets the list of specified type using the naming format inside ClusterCockpit
func GetTypeList(topology_type string) []int {
switch topology_type {
case "node":
return []int{0}
case "socket":
return SocketList()
case "die":
return DieList()
case "memoryDomain":
return NumaNodeList()
case "core":
return CoreList()
case "hwthread":
return HwthreadList()
}
return []int{}
}
// Structure holding all information about a hardware thread
type HwthreadEntry struct {
CpuID int
SMT int
Core int
Socket int
NumaDomain int
Die int
}
func CpuData() []HwthreadEntry {
getCore :=
func(basePath string) int {
return fileToInt(fmt.Sprintf("%s/core_id", basePath))
}
getSocket :=
func(basePath string) int {
return fileToInt(fmt.Sprintf("%s/physical_package_id", basePath))
}
getDie :=
func(basePath string) int {
return fileToInt(fmt.Sprintf("%s/die_id", basePath))
}
getSMT :=
func(cpuID int, basePath string) int {
buffer, err := os.ReadFile(fmt.Sprintf("%s/thread_siblings_list", basePath))
if err != nil {
cclogger.ComponentError("CCTopology", "CpuData:getSMT", err.Error())
}
threadList := make([]int, 0)
stringBuffer := strings.TrimSpace(string(buffer))
for _, x := range strings.Split(stringBuffer, ",") {
id, err := strconv.ParseInt(x, 10, 32)
if err != nil {
cclogger.ComponentError("CCTopology", "CpuData:getSMT", err.Error())
}
threadList = append(threadList, int(id))
}
if i := slices.Index(threadList, cpuID); i != -1 {
return i
}
return 1
}
getNumaDomain :=
func(basePath string) int {
globPath := filepath.Join(basePath, "node*")
regexPath := filepath.Join(basePath, "node([[:digit:]]+)")
regex := regexp.MustCompile(regexPath)
files, err := filepath.Glob(globPath)
if err != nil {
cclogger.ComponentError("CCTopology", "CpuData:getNumaDomain", err.Error())
}
for _, file := range files {
matches := regex.FindStringSubmatch(file)
fmt.Println(len(matches))
if len(matches) == 2 {
id, err := strconv.Atoi(matches[1])
if err == nil {
return id
}
}
}
return 0
}
cList := make([]HwthreadEntry, 0)
for _, c := range HwthreadList() {
cList = append(cList,
HwthreadEntry{
CpuID: c,
Socket: -1,
NumaDomain: -1,
Die: -1,
Core: -1,
})
}
for i := range cList {
cEntry := &cList[i]
// Set base directory for topology lookup
cpuStr := fmt.Sprintf("cpu%d", cEntry.CpuID)
base := filepath.Join("/sys/devices/system/cpu", cpuStr)
topoBase := filepath.Join(base, "topology")
// Lookup CPU core id
cEntry.Core = getCore(topoBase)
// Lookup CPU socket id
cEntry.Socket = getSocket(topoBase)
// Lookup CPU die id
cEntry.Die = getDie(topoBase)
if cEntry.Die < 0 {
cEntry.Die = cEntry.Socket
}
// Lookup SMT thread id
cEntry.SMT = getSMT(cEntry.CpuID, topoBase)
// Lookup NUMA domain id
cEntry.NumaDomain = getNumaDomain(base)
}
return cList
}
// Structure holding basic information about a CPU
type CpuInformation struct {
NumHWthreads int
SMTWidth int
NumSockets int
NumDies int
NumCores int
NumNumaDomains int
}
// Get basic information about the CPU
func CpuInfo() CpuInformation {
smtList := make([]int, 0)
numaList := make([]int, 0)
dieList := make([]int, 0)
socketList := make([]int, 0)
coreList := make([]int, 0)
cdata := CpuData()
for _, d := range cdata {
if ok := slices.Contains(smtList, d.SMT); !ok {
smtList = append(smtList, d.SMT)
}
if ok := slices.Contains(numaList, d.NumaDomain); !ok {
numaList = append(numaList, d.NumaDomain)
}
if ok := slices.Contains(dieList, d.Die); !ok {
dieList = append(dieList, d.Die)
}
if ok := slices.Contains(socketList, d.Socket); !ok {
socketList = append(socketList, d.Socket)
}
if ok := slices.Contains(coreList, d.Core); !ok {
coreList = append(coreList, d.Core)
}
}
return CpuInformation{
NumNumaDomains: len(numaList),
SMTWidth: len(smtList),
NumDies: len(dieList),
NumCores: len(coreList),
NumSockets: len(socketList),
NumHWthreads: len(cdata),
}
}
// Get the CPU socket ID for a given hardware thread ID
func GetHwthreadSocket(cpuid int) int {
cdata := CpuData()
for _, d := range cdata {
if d.CpuID == cpuid {
return d.Socket
}
}
return -1
}
// Get the NUMA node ID for a given hardware thread ID
func GetHwthreadNumaDomain(cpuid int) int {
cdata := CpuData()
for _, d := range cdata {
if d.CpuID == cpuid {
return d.NumaDomain
}
}
return -1
}
// Get the CPU die ID for a given hardware thread ID
func GetHwthreadDie(cpuid int) int {
cdata := CpuData()
for _, d := range cdata {
if d.CpuID == cpuid {
return d.Die
}
}
return -1
}
// Get the CPU core ID for a given hardware thread ID
func GetHwthreadCore(cpuid int) int {
cdata := CpuData()
for _, d := range cdata {
if d.CpuID == cpuid {
return d.Core
}
}
return -1
}
// Get the all hardware thread ID associated with a CPU socket
func GetSocketHwthreads(socket int) []int {
all := CpuData()
cpulist := make([]int, 0)
for _, d := range all {
if d.Socket == socket {
cpulist = append(cpulist, d.CpuID)
}
}
return cpulist
}
// Get the all hardware thread ID associated with a NUMA node
func GetNumaDomainHwthreads(domain int) []int {
all := CpuData()
cpulist := make([]int, 0)
for _, d := range all {
if d.NumaDomain == domain {
cpulist = append(cpulist, d.CpuID)
}
}
return cpulist
}
// Get the all hardware thread ID associated with a CPU die
func GetDieHwthreads(die int) []int {
all := CpuData()
cpulist := make([]int, 0)
for _, d := range all {
if d.Die == die {
cpulist = append(cpulist, d.CpuID)
}
}
return cpulist
}
// Get the all hardware thread ID associated with a CPU core
func GetCoreHwthreads(core int) []int {
all := CpuData()
cpulist := make([]int, 0)
for _, d := range all {
if d.Core == core {
cpulist = append(cpulist, d.CpuID)
}
}
return cpulist
}