diff --git a/pkg/cpuallocator/allocator.go b/pkg/cpuallocator/allocator.go index ea8ad4282..5be5b8c71 100644 --- a/pkg/cpuallocator/allocator.go +++ b/pkg/cpuallocator/allocator.go @@ -88,10 +88,10 @@ type topologyCache struct { pkg map[idset.ID]cpuset.CPUSet node map[idset.ID]cpuset.CPUSet core map[idset.ID]cpuset.CPUSet + kind map[sysfs.CoreKind]cpuset.CPUSet cpuPriorities cpuPriorities // CPU priority mapping clusters []*cpuCluster // CPU clusters - } type cpuPriorities [NumCPUPriorities]cpuset.CPUSet @@ -101,6 +101,7 @@ type cpuCluster struct { die idset.ID cluster idset.ID cpus cpuset.CPUSet + kind sysfs.CoreKind } // IDFilter helps filtering Ids. @@ -159,7 +160,14 @@ func (a *allocatorHelper) takeIdlePackages() { // pick idle packages pkgs := pickIds(a.sys.PackageIDs(), func(id idset.ID) bool { + // Consider a package idle if all online preferred CPUs are idle. + // In particular, on hybrid core architectures exclude + // - exclude E-cores from allocations with <= PriorityNormal preference + // - exclude P-cores from allocations with > PriorityLow preferences cset := a.topology.pkg[id].Difference(offline) + if a.prefer < NumCPUPriorities { + cset = cset.Intersection(a.topology.cpuPriorities[a.prefer]) + } return cset.Intersection(a.from).Equals(cset) }) @@ -177,6 +185,9 @@ func (a *allocatorHelper) takeIdlePackages() { // take as many idle packages as we need/can for _, id := range pkgs { cset := a.topology.pkg[id].Difference(offline) + if a.prefer < NumCPUPriorities { + cset = cset.Intersection(a.topology.cpuPriorities[a.prefer]) + } a.Debug(" => considering package %v (#%s)...", id, cset) if a.cnt >= cset.Size() { a.Debug(" => taking package %v...", id) @@ -200,6 +211,17 @@ func (a *allocatorHelper) takeIdleClusters() { var ( offline = a.sys.OfflineCPUs() pickIdle = func(c *cpuCluster) (bool, cpuset.CPUSet) { + // we only take E-clusters for low-prio requests + if a.prefer != PriorityLow && c.kind == sysfs.EfficientCore { + a.Debug(" - omit %s, CPU preference is %s", c, a.prefer) + return false, emptyCPUSet + } + // we only take P-clusters for other than low-prio requests + if a.prefer == PriorityLow && c.kind == sysfs.PerformanceCore { + a.Debug(" - omit %s, CPU preference is %s", c, a.prefer) + return false, emptyCPUSet + } + // we only take fully idle clusters cset := c.cpus.Difference(offline) free := cset.Intersection(a.from) @@ -692,9 +714,17 @@ func (c *topologyCache) discoverCPUPriorities(sys sysfs.System) { cpuPriorities = c.discoverCpufreqPriority(sys, id) } + ecores := c.kind[sysfs.EfficientCore] + ocores := sys.OnlineCPUs().Difference(ecores) + for p, cpus := range cpuPriorities { source := map[bool]string{true: "sst", false: "cpufreq"}[sstActive] cset := sysfs.CPUSetFromIDSet(idset.NewIDSet(cpus...)) + + if p != int(PriorityLow) && ocores.Size() > 0 { + cset = cset.Difference(ecores) + } + log.Debug("package #%d (%s): %d %s priority cpus (%v)", id, source, len(cpus), CPUPriority(p), cset) prio[p] = prio[p].Union(cset) } @@ -826,7 +856,7 @@ func (c *topologyCache) sstClosPriority(sys sysfs.System, pkgID idset.ID) map[in func (c *topologyCache) discoverCpufreqPriority(sys sysfs.System, pkgID idset.ID) [NumCPUPriorities][]idset.ID { var prios [NumCPUPriorities][]idset.ID - // Group cpus by base frequency and energy performance profile + // Group cpus by base frequency, core kind and energy performance profile freqs := map[uint64][]idset.ID{} epps := map[sysfs.EPP][]idset.ID{} cpuIDs := c.pkg[pkgID].List() @@ -874,14 +904,19 @@ func (c *topologyCache) discoverCpufreqPriority(sys sysfs.System, pkgID idset.ID } } - // All cpus NOT in the lowest performance epp are considered high prio + // All E-cores are unconditionally considered low prio. + // All cpus NOT in the lowest performance epp are considered high prio. // NOTE: higher EPP value denotes lower performance preference - if len(eppList) > 1 { - epp := cpu.EPP() - if int(epp) < eppList[len(eppList)-1] { - p = PriorityHigh - } else { - p = PriorityLow + if cpu.CoreKind() == sysfs.EfficientCore { + p = PriorityLow + } else { + if len(eppList) > 1 { + epp := cpu.EPP() + if int(epp) < eppList[len(eppList)-1] { + p = PriorityHigh + } else { + p = PriorityLow + } } } @@ -907,18 +942,24 @@ func (c *topologyCache) discoverCPUClusters(sys sysfs.System) { die: die, cluster: cl, cpus: cpus, + kind: sys.CPU(cpus.List()[0]).CoreKind(), }) } } if len(clusters) > 1 { log.Debug("package #%d has %d clusters:", id, len(clusters)) for _, cl := range clusters { - log.Debug(" die #%d, cluster #%d: cpus %s", - cl.die, cl.cluster, cl.cpus) + log.Debug(" die #%d, cluster #%d: %s cpus %s", + cl.die, cl.cluster, cl.kind, cl.cpus) } c.clusters = append(c.clusters, clusters...) } } + + c.kind = map[sysfs.CoreKind]cpuset.CPUSet{} + for _, kind := range sys.CoreKinds() { + c.kind[kind] = sys.CoreKindCPUs(kind) + } } func (p CPUPriority) String() string { @@ -943,6 +984,38 @@ func (c *cpuPriorities) cmpCPUSet(csetA, csetB cpuset.CPUSet, prefer CPUPriority return 0 } + // For low prio request, favor cpuset with the tightest fit. + if cpuCnt > 0 && prefer == PriorityLow { + prefA := csetA.Intersection(c[prefer]).Size() + prefB := csetB.Intersection(c[prefer]).Size() + // both sets have enough preferred CPUs, return the smaller one (tighter fit) + if prefA >= cpuCnt && prefB >= cpuCnt { + return prefB - prefA + } + // only one set has enough preferred CPUs, return the bigger/only one + if prefA >= cpuCnt || prefB >= cpuCnt { + return prefA - prefB + } + } + + // For high prio request, favor the tightest fit falling back to normal prio + if cpuCnt > 0 && prefer == PriorityHigh { + prefA := csetA.Intersection(c[prefer]).Size() + prefB := csetB.Intersection(c[prefer]).Size() + if prefA == 0 && prefB == 0 { + prefA = csetA.Intersection(c[PriorityNormal]).Size() + prefB = csetB.Intersection(c[PriorityNormal]).Size() + } + // both sets have enough preferred CPUs, return the smaller one (tighter fit) + if prefA >= cpuCnt && prefB >= cpuCnt { + return prefB - prefA + } + // only one set has enough preferred CPUs, return the bigger/only one + if prefA >= cpuCnt || prefB >= cpuCnt { + return prefA - prefB + } + } + // Favor cpuset having CPUs with priorities equal to or lower than what was requested for prio := prefer; prio < NumCPUPriorities; prio++ { prefA := csetA.Intersection(c[prio]).Size() @@ -984,6 +1057,6 @@ func (c *cpuCluster) HasSmallerIDsThan(o *cpuCluster) bool { } func (c *cpuCluster) String() string { - return fmt.Sprintf("cluster #%d/%d/%d, %d CPUs (%s)", c.pkg, c.die, c.cluster, - c.cpus.Size(), c.cpus) + return fmt.Sprintf("cluster #%d/%d/%d, %d %s CPUs (%s)", c.pkg, c.die, c.cluster, + c.cpus.Size(), c.kind, c.cpus) } diff --git a/pkg/cpuallocator/cpuallocator_test.go b/pkg/cpuallocator/cpuallocator_test.go index bfaeed5d6..c4dd6dc3b 100644 --- a/pkg/cpuallocator/cpuallocator_test.go +++ b/pkg/cpuallocator/cpuallocator_test.go @@ -317,3 +317,433 @@ func TestClusteredAllocation(t *testing.T) { }) } } + +func TestClusteredCoreKindAllocation(t *testing.T) { + if v := os.Getenv("ENABLE_DEBUG"); v != "" { + logger.EnableDebug(logSource) + } + + // Create tmpdir and decompress testdata there + tmpdir, err := ioutil.TempDir("", "nri-resource-policy-test-") + if err != nil { + t.Fatalf("failed to create tmpdir: %v", err) + } + defer os.RemoveAll(tmpdir) + + if err := utils.UncompressTbz2(path.Join("testdata", "sysfs.tar.bz2"), tmpdir); err != nil { + t.Fatalf("failed to decompress testdata: %v", err) + } + + // Discover mock system from the testdata + sys, err := sysfs.DiscoverSystemAt( + path.Join(tmpdir, "sysfs", "2-socket-4-node-40-core", "sys"), + sysfs.DiscoverCPUTopology, sysfs.DiscoverMemTopology) + if err != nil { + t.Fatalf("failed to discover mock system: %v", err) + } + + cluster1 := []*cpuCluster{ + { + pkg: 0, + die: 0, + cluster: 0, + cpus: cpuset.MustParse("0-3"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 1, + cpus: cpuset.MustParse("4-7"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 2, + cpus: cpuset.MustParse("8-11"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 3, + cpus: cpuset.MustParse("12-15"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 4, + cpus: cpuset.MustParse("16-19"), + kind: sysfs.EfficientCore, + }, + { + pkg: 0, + die: 0, + cluster: 5, + cpus: cpuset.MustParse("40-43"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 6, + cpus: cpuset.MustParse("44-47"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 7, + cpus: cpuset.MustParse("48-51"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 8, + cpus: cpuset.MustParse("52-55"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 9, + cpus: cpuset.MustParse("56-59"), + kind: sysfs.EfficientCore, + }, + + { + pkg: 1, + die: 0, + cluster: 0, + cpus: cpuset.MustParse("20,22,24,26"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 1, + cpus: cpuset.MustParse("21,23,25,27"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 2, + cpus: cpuset.MustParse("28-31"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 3, + cpus: cpuset.MustParse("32-35"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 4, + cpus: cpuset.MustParse("36-39"), + kind: sysfs.EfficientCore, + }, + { + pkg: 1, + die: 0, + cluster: 5, + cpus: cpuset.MustParse("60-63"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 6, + cpus: cpuset.MustParse("64-67"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 7, + cpus: cpuset.MustParse("68-71"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 8, + cpus: cpuset.MustParse("72-75"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 9, + cpus: cpuset.MustParse("76-79"), + kind: sysfs.EfficientCore, + }, + } + + cluster2 := []*cpuCluster{ + { + pkg: 0, + die: 0, + cluster: 0, + cpus: cpuset.MustParse("0-3"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 1, + cpus: cpuset.MustParse("4-7"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 2, + cpus: cpuset.MustParse("8-11"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 3, + cpus: cpuset.MustParse("12-15"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 4, + cpus: cpuset.MustParse("16-19"), + kind: sysfs.EfficientCore, + }, + { + pkg: 0, + die: 0, + cluster: 5, + cpus: cpuset.MustParse("40-43"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 6, + cpus: cpuset.MustParse("44-47"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 7, + cpus: cpuset.MustParse("48-51"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 8, + cpus: cpuset.MustParse("52-55"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 0, + die: 0, + cluster: 9, + cpus: cpuset.MustParse("56-59"), + kind: sysfs.EfficientCore, + }, + + { + pkg: 1, + die: 0, + cluster: 0, + cpus: cpuset.MustParse("20,22,24,26"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 1, + cpus: cpuset.MustParse("21,23,25,27"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 2, + cpus: cpuset.MustParse("28-31"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 3, + cpus: cpuset.MustParse("32-35"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 4, + cpus: cpuset.MustParse("36-37"), + kind: sysfs.EfficientCore, + }, + { + pkg: 1, + die: 0, + cluster: 5, + cpus: cpuset.MustParse("38-39"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 6, + cpus: cpuset.MustParse("60-63"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 7, + cpus: cpuset.MustParse("64-67"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 8, + cpus: cpuset.MustParse("68-71"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 9, + cpus: cpuset.MustParse("72-75"), + kind: sysfs.PerformanceCore, + }, + { + pkg: 1, + die: 0, + cluster: 10, + cpus: cpuset.MustParse("76-79"), + kind: sysfs.EfficientCore, + }, + } + + pkg0 := cpuset.MustParse("0-19,40-59") + pkg1 := cpuset.MustParse("20-39,60-79") + all := pkg0.Union(pkg1) + + tcs := []struct { + description string + clusters []*cpuCluster + from cpuset.CPUSet + prefer CPUPriority + cnt int + expected cpuset.CPUSet + }{ + { + description: "P-cores worth one cluster", + clusters: cluster1, + from: all, + prefer: PriorityNormal, + cnt: 4, + expected: cpuset.MustParse("0-3"), + }, + { + description: "P-cores worth 2 clusters", + clusters: cluster1, + from: all, + prefer: PriorityNormal, + cnt: 8, + expected: cpuset.MustParse("0-7"), + }, + { + description: "P-cores worth all clusters in a package", + clusters: cluster1, + from: all, + prefer: PriorityNormal, + cnt: 32, + expected: cpuset.MustParse("0-15,40-55"), + }, + { + description: "E-cores worth 1 cluster", + clusters: cluster1, + from: all, + prefer: PriorityLow, + cnt: 4, + expected: cpuset.MustParse("16-19"), + }, + { + description: "E-cores worth 2 clusters", + clusters: cluster1, + from: all, + prefer: PriorityLow, + cnt: 8, + expected: cpuset.MustParse("16-19,56-59"), + }, + { + description: "P-cores worth 1 cluster more than in the 1st package", + clusters: cluster1, + from: all, + prefer: PriorityNormal, + cnt: 36, + expected: cpuset.MustParse("0-15,40-55,20,22,24,26"), + }, + { + description: "P-cores worth 2 clusters more than in the 1st package", + clusters: cluster1, + from: all, + prefer: PriorityNormal, + cnt: 40, + expected: cpuset.MustParse("0-15,20-27,40-55"), + }, + { + description: "E-cores worth 1 clusters, should take tighter fit", + clusters: cluster2, + from: all, + prefer: PriorityLow, + cnt: 2, + expected: cpuset.MustParse("36-37"), + }, + { + description: "E-cores worth 2 clusters, should take tighter fit", + clusters: cluster2, + from: all, + prefer: PriorityLow, + cnt: 6, + expected: cpuset.MustParse("36-37,76-79"), + }, + { + description: "E-cores worth 2 clusters, should take single die", + clusters: cluster2, + from: all, + prefer: PriorityLow, + cnt: 8, + expected: cpuset.MustParse("16-19,56-59"), + }, + } + + // Run tests + for _, tc := range tcs { + t.Run(tc.description, func(t *testing.T) { + topoCache := newTopologyCache(sys) + topoCache.clusters = tc.clusters + a := newAllocatorHelper(sys, topoCache) + a.from = tc.from + a.prefer = tc.prefer + a.cnt = tc.cnt + result := a.allocate() + if !result.Equals(tc.expected) { + t.Errorf("expected %q, result was %q", tc.expected, result) + } + }) + } +} diff --git a/pkg/cpuallocator/testdata/sysfs.tar.bz2 b/pkg/cpuallocator/testdata/sysfs.tar.bz2 index 715dcf3eb..ea325d12f 100644 Binary files a/pkg/cpuallocator/testdata/sysfs.tar.bz2 and b/pkg/cpuallocator/testdata/sysfs.tar.bz2 differ diff --git a/pkg/sysfs/system.go b/pkg/sysfs/system.go index 606e36e28..0cd8bcc80 100644 --- a/pkg/sysfs/system.go +++ b/pkg/sysfs/system.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "io/fs" + "os" "path/filepath" "sort" "strconv" @@ -100,6 +101,9 @@ type System interface { OnlineCPUs() cpuset.CPUSet IsolatedCPUs() cpuset.CPUSet OfflineCPUs() cpuset.CPUSet + CoreKindCPUs(CoreKind) cpuset.CPUSet + CoreKinds() []CoreKind + AllThreadsForCPUs(cpuset.CPUSet) cpuset.CPUSet Offlined() cpuset.CPUSet Isolated() cpuset.CPUSet @@ -118,6 +122,7 @@ type system struct { presentCPUs idset.IDSet // set of present CPUs onlineCPUs idset.IDSet // set of online CPUs isolatedCPUs idset.IDSet // set of isolated CPUs + coreKindCPUs map[CoreKind]idset.IDSet // CPU cores by kind (P-/E-cores) threads int // hyperthreads per core } @@ -194,6 +199,7 @@ type CPU interface { GetCacheByIndex(int) *Cache GetLastLevelCaches() []*Cache GetLastLevelCacheCPUSet() cpuset.CPUSet + CoreKind() CoreKind } type cpu struct { @@ -212,6 +218,7 @@ type cpu struct { isolated bool // whether this CPU is isolated sstClos int // SST-CP CLOS the CPU is associated with caches []*Cache // caches for this CPU + coreKind CoreKind // P- or E-core } // CPUFreq is a CPU frequency scaling range @@ -232,6 +239,29 @@ const ( EPPUnknown ) +// CoreKind represents high-level classification of CPU cores, currently P- and E-cores +type CoreKind int + +const ( + PerformanceCore CoreKind = iota + EfficientCore +) + +var ( + coreKindCPUPath = map[CoreKind]string{ + PerformanceCore: "devices/cpu_core/cpus", + EfficientCore: "devices/cpu_atom/cpus", + } + coreKindNames = map[CoreKind]string{ + PerformanceCore: "P-core", + EfficientCore: "E-core", + } + coreKindEnvOverrides = map[CoreKind]string{ + PerformanceCore: "OVERRIDE_SYS_CORE_CPUS", + EfficientCore: "OVERRIDE_SYS_ATOM_CPUS", + } +) + // MemInfo contains data read from a NUMA node meminfo file. type MemInfo struct { MemTotal uint64 @@ -352,6 +382,12 @@ func (sys *system) Discover(flags DiscoveryFlag) error { sys.Debug(" - offline: %s", sys.OfflineCPUs()) sys.Debug(" - isolated: %s", sys.IsolatedCPUs()) + for kind, name := range coreKindNames { + if cpus := sys.CoreKindCPUs(kind); !cpus.IsEmpty() { + sys.Debug(" - %8s: %s", name, sys.CoreKindCPUs(kind)) + } + } + for _, id := range sys.PackageIDs() { pkg := sys.packages[id] sys.Info("package #%d:", id) @@ -388,7 +424,7 @@ func (sys *system) Discover(flags DiscoveryFlag) error { sys.Debug(" die: %d", cpu.die) sys.Debug(" cluster: %d", cpu.cluster) sys.Debug(" node: %d", cpu.node) - sys.Debug(" core: %d", cpu.core) + sys.Debug(" core: %d (%s)", cpu.core, cpu.coreKind) sys.Debug(" threads: %s", cpu.threads) sys.Debug(" base freq: %d", cpu.baseFreq) sys.Debug(" freq: %d - %d", cpu.freq.min, cpu.freq.max) @@ -599,6 +635,30 @@ func (sys *system) OfflineCPUs() cpuset.CPUSet { return CPUSetFromIDSet(offline) } +// CoreKindCPUs gets the set of CPU cores by kind. +func (sys *system) CoreKindCPUs(kind CoreKind) cpuset.CPUSet { + return CPUSetFromIDSet(sys.coreKindCPUs[kind]) +} + +// CoreKinds gets CPU cores kinds present in the system. +func (sys *system) CoreKinds() []CoreKind { + kinds := []CoreKind{} + for kind := range sys.coreKindCPUs { + kinds = append(kinds, kind) + } + return kinds +} + +func (sys *system) AllThreadsForCPUs(cpus cpuset.CPUSet) cpuset.CPUSet { + all := cpuset.New() + for _, id := range cpus.UnsortedList() { + if cpu, ok := sys.cpus[id]; ok { + all = all.Union(cpu.ThreadCPUSet()) + } + } + return all +} + // Offlined gets the set of offlined CPUs. func (sys *system) Offlined() cpuset.CPUSet { return sys.OfflineCPUs() @@ -638,6 +698,37 @@ func (sys *system) discoverCPUs() error { sys.Error("failed to get set of isolated cpus: %v", err) } + sys.coreKindCPUs = make(map[CoreKind]idset.IDSet) + + for kind, name := range coreKindEnvOverrides { + if override := os.Getenv(name); override != "" { + log.Warn("using CPU core kind environment override (%s=%s)...", name, override) + cpus, err := cpuset.Parse(override) + if err != nil { + return fmt.Errorf("failed to parse %s env. override %q: %v", kind, override, err) + } + if cpus.Size() > 0 { + sys.coreKindCPUs[kind] = idset.NewIDSet(cpus.UnsortedList()...) + } + } + } + + if len(sys.coreKindCPUs) == 0 { + for kind, entry := range coreKindCPUPath { + cpus := idset.NewIDSet() + _, err = readSysfsEntry(sys.path, entry, &cpus, ",") + if err != nil { + sys.Error("failed to get set of %s cpus: %v", kind, err) + if kind == PerformanceCore { + cpus = sys.onlineCPUs.Clone() + } + } + if cpus.Size() > 0 { + sys.coreKindCPUs[kind] = cpus + } + } + } + entries, _ := filepath.Glob(filepath.Join(sys.path, sysfsCPUPath, "cpu[0-9]*")) for _, entry := range entries { if err := sys.discoverCPU(entry); err != nil { @@ -645,6 +736,89 @@ func (sys *system) discoverCPUs() error { } } + if err := sys.checkCoreKinds(); err != nil { + return err + } + + return nil +} + +// Perform a basic sanity checks of hybrid cores. +func (sys *system) checkCoreKinds() error { + switch len(sys.coreKindCPUs) { + case 0: + // If we have not detected any explicit core types, assume all cores to be P-cores. + sys.coreKindCPUs[PerformanceCore] = sys.onlineCPUs.Clone() + + case 1: + // Allow and fix up partial core type overrides. If we only have one core type, + // expand that type to be thread-complete. Since currently we only know of two + // core types, set up the other type to cover all the remaining/missing cores. + for kind, ids := range sys.coreKindCPUs { + given := kind + gcset := sys.AllThreadsForCPUs(CPUSetFromIDSet(ids)) + + if !gcset.Equals(sys.OnlineCPUs()) { + var other CoreKind + + if given == PerformanceCore { + other = EfficientCore + } else { + other = PerformanceCore + } + + ocset := sys.OnlineCPUs().Difference(gcset) + sys.coreKindCPUs[given] = idset.NewIDSet(gcset.UnsortedList()...) + sys.coreKindCPUs[other] = idset.NewIDSet(ocset.UnsortedList()...) + break + } + } + } + + // Perform sanity checks on the core types: + // - all core types must be thread-complete. + // - a core can be of one type only + // - all cores must be of some type + + var ( + kinds = map[CoreKind]cpuset.CPUSet{} + all = cpuset.New() + ) + + for kind := range sys.coreKindCPUs { + cores := sys.CoreKindCPUs(kind) + + // core types must be thread-complete + if missing := sys.AllThreadsForCPUs(cores).Difference(cores); !missing.IsEmpty() { + return fmt.Errorf("%s CPUs (%s) miss threads (%s)", kind, cores, missing) + } + + // a core can belong to only one type + for k, c := range kinds { + if common := cores.Intersection(c); !common.IsEmpty() { + return fmt.Errorf("%s CPUs (%s) and %s CPUs (%s) overlap (%s)", + kind, cores, k, c, common) + } + } + + kinds[kind] = cores + all = all.Union(cores) + } + + // all cores must be of sometype + if missing := sys.OnlineCPUs().Difference(all); !missing.IsEmpty() { + return fmt.Errorf("some CPUs (%s) are neither marked to be of any core type", missing) + } + + // set/store core types per CPU + for kind, ids := range sys.coreKindCPUs { + for id := range ids { + if cpu, ok := sys.cpus[id]; ok { + cpu.coreKind = kind + } + } + } + return nil } @@ -890,6 +1064,11 @@ func (c *cpu) GetLastLevelCacheCPUSet() cpuset.CPUSet { return cpus } +// CoreKind returns the core kind (P-/E-core) for this CPU. +func (c *cpu) CoreKind() CoreKind { + return c.coreKind +} + func (c *Cache) ID() int { if c == nil { return 0 @@ -1454,3 +1633,7 @@ func (t CacheType) String() string { } return "" } + +func (k CoreKind) String() string { + return coreKindNames[k] +} diff --git a/pkg/sysfs/system_test.go b/pkg/sysfs/system_test.go index 5c11284c5..d172ecc71 100644 --- a/pkg/sysfs/system_test.go +++ b/pkg/sysfs/system_test.go @@ -29,6 +29,7 @@ type ( ID = idset.ID System = sysfs.System CacheType = sysfs.CacheType + CoreKind = sysfs.CoreKind ) var ( @@ -42,6 +43,9 @@ const ( Instruction = sysfs.InstructionCache Unified = sysfs.UnifiedCache K = uint64(1024) + + PerformanceCore = sysfs.PerformanceCore + EfficientCore = sysfs.EfficientCore ) var _ = BeforeSuite(func() { @@ -195,3 +199,15 @@ var _ = DescribeTable("Logical/hyperthread-filtered cluster CPUSet", Entry("die #0, cluster #32", "sample1", 0, 0, 32, "8-11"), Entry("die #0, cluster #40", "sample1", 0, 0, 40, "12-15"), ) + +var _ = DescribeTable("P-/E-Core CPU detection", + func(sample string, kind CoreKind, cpus string) { + sys := sampleSysfs[sample] + Expect(sys).ToNot(BeNil()) + cset := sys.CoreKindCPUs(kind) + Expect(cset.String()).To(Equal(cpus)) + }, + + Entry("P-Cores", "sample1", PerformanceCore, "0-7"), + Entry("E-Cores", "sample1", EfficientCore, "8-15"), +) diff --git a/pkg/sysfs/test-data-sample1.tar.xz b/pkg/sysfs/test-data-sample1.tar.xz index 8d57e5ddb..39535d63d 100644 Binary files a/pkg/sysfs/test-data-sample1.tar.xz and b/pkg/sysfs/test-data-sample1.tar.xz differ