open-nomad/lib/cpuset/cpuset.go

200 lines
4.4 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cpuset
import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
)
// CPUSet is a set like object that provides methods helpful when working with cpus with systems
// such as the Linux cpuset cgroup subsystem. A CPUSet is immutable and can be safely accessed concurrently.
type CPUSet struct {
cpus map[uint16]struct{}
}
// New initializes a new CPUSet with 0 or more containing cpus
func New(cpus ...uint16) CPUSet {
cpuset := CPUSet{
cpus: make(map[uint16]struct{}),
}
for _, v := range cpus {
cpuset.cpus[v] = struct{}{}
}
return cpuset
}
// Copy returns a deep copy of CPUSet c.
func (c CPUSet) Copy() CPUSet {
cpus := make(map[uint16]struct{}, len(c.cpus))
for k := range c.cpus {
cpus[k] = struct{}{}
}
return CPUSet{
cpus: cpus,
}
}
// String returns the cpuset as a comma delimited set of core values and ranged
func (c CPUSet) String() string {
if c.Size() == 0 {
return ""
}
cores := c.ToSlice()
cpusetStrs := []string{}
cur := [2]uint16{cores[0], cores[0]}
for i := 1; i < len(cores); i++ {
if cores[i] == cur[1]+1 {
cur[1] = cores[i]
continue
}
if cur[0] == cur[1] {
cpusetStrs = append(cpusetStrs, fmt.Sprintf("%d", cur[0]))
} else {
cpusetStrs = append(cpusetStrs, fmt.Sprintf("%d-%d", cur[0], cur[1]))
}
// new range
cur = [2]uint16{cores[i], cores[i]}
}
if cur[0] == cur[1] {
cpusetStrs = append(cpusetStrs, fmt.Sprintf("%d", cur[0]))
} else {
cpusetStrs = append(cpusetStrs, fmt.Sprintf("%d-%d", cur[0], cur[1]))
}
return strings.Join(cpusetStrs, ",")
}
// Size returns to the number of cpus contained in the CPUSet
func (c CPUSet) Size() int {
return len(c.cpus)
}
// ToSlice returns a sorted slice of uint16 CPU IDs contained in the CPUSet.
func (c CPUSet) ToSlice() []uint16 {
cpus := []uint16{}
for k := range c.cpus {
cpus = append(cpus, k)
}
sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
return cpus
}
// Union returns a new set that is the union of this CPUSet and the supplied other.
// Ex. [0,1,2,3].Union([2,3,4,5]) = [0,1,2,3,4,5]
func (c CPUSet) Union(other CPUSet) CPUSet {
s := New()
for k := range c.cpus {
s.cpus[k] = struct{}{}
}
for k := range other.cpus {
s.cpus[k] = struct{}{}
}
return s
}
// Difference returns a new set that is the difference of this CPUSet and the supplied other.
// [0,1,2,3].Difference([2,3,4]) = [0,1]
func (c CPUSet) Difference(other CPUSet) CPUSet {
s := New()
for k := range c.cpus {
s.cpus[k] = struct{}{}
}
for k := range other.cpus {
delete(s.cpus, k)
}
return s
}
// IsSubsetOf returns true if all cpus of the this CPUSet are present in the other CPUSet.
func (c CPUSet) IsSubsetOf(other CPUSet) bool {
for cpu := range c.cpus {
if _, ok := other.cpus[cpu]; !ok {
return false
}
}
return true
}
func (c CPUSet) IsSupersetOf(other CPUSet) bool {
for cpu := range other.cpus {
if _, ok := c.cpus[cpu]; !ok {
return false
}
}
return true
}
// ContainsAny returns true if any cpus in other CPUSet are present
func (c CPUSet) ContainsAny(other CPUSet) bool {
for cpu := range other.cpus {
if _, ok := c.cpus[cpu]; ok {
return true
}
}
return false
}
// Equal tests the equality of the elements in the CPUSet
func (c CPUSet) Equal(other CPUSet) bool {
return reflect.DeepEqual(c.cpus, other.cpus)
}
// Parse parses the Linux cpuset format into a CPUSet
//
// Ref: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
func Parse(s string) (CPUSet, error) {
cpuset := New()
s = strings.TrimSpace(s)
if s == "" {
return cpuset, nil
}
sets := strings.Split(s, ",")
for _, set := range sets {
bounds := strings.Split(set, "-")
if len(bounds) == 1 {
v, err := strconv.Atoi(bounds[0])
if err != nil {
return New(), err
}
if v > math.MaxUint16 {
return New(), fmt.Errorf("failed to parse element %s, more than max allowed cores", set)
}
cpuset.cpus[uint16(v)] = struct{}{}
continue
}
if len(bounds) > 2 {
return New(), fmt.Errorf("failed to parse element %s, more than 1 '-' found", set)
}
lower, err := strconv.Atoi(bounds[0])
if err != nil {
return New(), err
}
upper, err := strconv.Atoi(bounds[1])
if err != nil {
return New(), err
}
for v := lower; v <= upper; v++ {
if v > math.MaxUint16 {
return New(), fmt.Errorf("failed to parse element %s, more than max allowed cores", set)
}
cpuset.cpus[uint16(v)] = struct{}{}
}
}
return cpuset, nil
}