mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 20:57:02 +02:00
119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
//go:build linux
|
|
|
|
package cpupick
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// readTopology probes the NUMA node and physical-core layout of cpus from
|
|
// sysfs. Anything sysfs won't say degrades toward flatTopology: an unknown
|
|
// node becomes node 0, an unknown core becomes a core of its own — either
|
|
// way the corresponding arrange rule becomes a no-op instead of a wrong
|
|
// answer.
|
|
func readTopology(cpus []int) topology {
|
|
return readTopologyFrom("/sys/devices/system/node", "/sys/devices/system/cpu", cpus)
|
|
}
|
|
|
|
func readTopologyFrom(nodeDir, cpuDir string, cpus []int) topology {
|
|
coreOf, zeroCore := coreGroups(cpuDir, cpus)
|
|
return topology{
|
|
nodeOf: numaNodes(nodeDir, cpus),
|
|
coreOf: coreOf,
|
|
zeroCore: zeroCore,
|
|
}
|
|
}
|
|
|
|
// numaNodes maps each cpu to its NUMA node via
|
|
// /sys/devices/system/node/nodeN/cpulist. CPUs no node claims (or no node
|
|
// dirs at all: VMs, non-NUMA kernels) land on node 0.
|
|
func numaNodes(nodeDir string, cpus []int) map[int]int {
|
|
out := make(map[int]int, len(cpus))
|
|
for _, c := range cpus {
|
|
out[c] = 0
|
|
}
|
|
entries, err := os.ReadDir(nodeDir)
|
|
if err != nil {
|
|
return out
|
|
}
|
|
want := make(map[int]bool, len(cpus))
|
|
for _, c := range cpus {
|
|
want[c] = true
|
|
}
|
|
for _, e := range entries {
|
|
id, ok := strings.CutPrefix(e.Name(), "node")
|
|
if !ok {
|
|
continue
|
|
}
|
|
n, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
continue // has_cpu, possible, ... share the prefix
|
|
}
|
|
b, err := os.ReadFile(filepath.Join(nodeDir, e.Name(), "cpulist"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
list, err := parseCPUList(strings.TrimSpace(string(b)))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, c := range list {
|
|
if want[c] {
|
|
out[c] = n
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// coreGroups maps each cpu to a dense physical-core id derived from its
|
|
// (physical_package_id, core_id) pair — core_id alone repeats across
|
|
// sockets. CPUs whose topology files are unreadable get a core of their own.
|
|
// The second return is the group id of the core CPU 0 lives on, or -1 when
|
|
// that can't be determined; CPU 0's own files are consulted even when 0 is
|
|
// not a candidate, so its SMT siblings are recognized under cpusets that
|
|
// exclude CPU 0 itself.
|
|
func coreGroups(cpuDir string, cpus []int) (map[int]int, int) {
|
|
type pkgCore struct{ pkg, core int }
|
|
pairOf := func(cpu int) (pkgCore, bool) {
|
|
topoDir := filepath.Join(cpuDir, fmt.Sprintf("cpu%d", cpu), "topology")
|
|
pkg, err1 := readIntFile(filepath.Join(topoDir, "physical_package_id"))
|
|
core, err2 := readIntFile(filepath.Join(topoDir, "core_id"))
|
|
if err1 != nil || err2 != nil {
|
|
return pkgCore{}, false
|
|
}
|
|
return pkgCore{pkg, core}, true
|
|
}
|
|
|
|
ids := map[pkgCore]int{}
|
|
out := make(map[int]int, len(cpus))
|
|
next := 0
|
|
for _, cpu := range cpus {
|
|
k, ok := pairOf(cpu)
|
|
if !ok {
|
|
out[cpu] = next
|
|
next++
|
|
continue
|
|
}
|
|
id, ok := ids[k]
|
|
if !ok {
|
|
id = next
|
|
next++
|
|
ids[k] = id
|
|
}
|
|
out[cpu] = id
|
|
}
|
|
|
|
zeroCore := -1
|
|
if k, ok := pairOf(0); ok {
|
|
if id, ok := ids[k]; ok {
|
|
zeroCore = id
|
|
}
|
|
}
|
|
return out, zeroCore
|
|
}
|