github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/machine/cpuset.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package machine
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"k8s.io/klog/v2"
    28  )
    29  
    30  type CPUSet struct {
    31  	// nil elems and empty elems both will be unmarshal to
    32  	// empty elems, so we must use Initialed property to identify them
    33  	Initialed bool
    34  	elems     map[int]struct{}
    35  }
    36  
    37  func NewCPUSet(cpus ...int) CPUSet {
    38  	cs := CPUSet{true, make(map[int]struct{})}
    39  	cs.Add(cpus...)
    40  
    41  	return cs
    42  }
    43  
    44  func NewCPUSetUint64(cpus ...uint64) (CPUSet, error) {
    45  	cs := CPUSet{true, make(map[int]struct{})}
    46  	err := cs.AddUint64(cpus...)
    47  	if err != nil {
    48  		return cs, err
    49  	}
    50  
    51  	return cs, nil
    52  }
    53  
    54  func (s CPUSet) Clone() CPUSet {
    55  	s2 := NewCPUSet()
    56  	for elem := range s.elems {
    57  		s2.Add(elem)
    58  	}
    59  	return s2
    60  }
    61  
    62  func (s *CPUSet) UnmarshalJSON(b []byte) error {
    63  	if len(b) < 2 || b[0] != byte('"') || b[len(b)-1] != byte('"') {
    64  		return fmt.Errorf("invalid cpuset string")
    65  	}
    66  
    67  	b = b[1 : len(b)-1]
    68  	if string(b) == "nil" {
    69  		*s = CPUSet{}
    70  		return nil
    71  	}
    72  
    73  	cs, err := Parse(string(b))
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	*s = cs
    79  	return nil
    80  }
    81  
    82  func (s CPUSet) MarshalJSON() ([]byte, error) {
    83  	if s.Initialed {
    84  		return []byte(fmt.Sprintf(`"%s"`, s.String())), nil
    85  	} else {
    86  		return []byte(`"nil"`), nil
    87  	}
    88  }
    89  
    90  // Add adds the supplied elements to the result.
    91  func (s CPUSet) Add(elems ...int) {
    92  	for _, elem := range elems {
    93  		s.elems[elem] = struct{}{}
    94  	}
    95  }
    96  
    97  // AddUint64 adds the supplied uint64 elements to the result.
    98  func (s CPUSet) AddUint64(elems ...uint64) error {
    99  	for _, elem := range elems {
   100  		elemInt := int(elem)
   101  
   102  		if elemInt < 0 || uint64(elemInt) != elem {
   103  			return fmt.Errorf("parse elem: %d to int failed", elem)
   104  		}
   105  
   106  		s.Add(elemInt)
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // Size returns the number of elements in this set.
   113  func (s CPUSet) Size() int {
   114  	return len(s.elems)
   115  }
   116  
   117  // IsEmpty returns true if there are zero elements in this set.
   118  func (s CPUSet) IsEmpty() bool {
   119  	return s.Size() == 0
   120  }
   121  
   122  // Contains returns true if the supplied element is present in this set.
   123  func (s CPUSet) Contains(cpu int) bool {
   124  	_, found := s.elems[cpu]
   125  	return found
   126  }
   127  
   128  // Equals returns true if the supplied set contains exactly the same elements
   129  // as this set (s IsSubsetOf s2 and s2 IsSubsetOf s).
   130  func (s CPUSet) Equals(s2 CPUSet) bool {
   131  	return reflect.DeepEqual(s.elems, s2.elems)
   132  }
   133  
   134  // Filter returns a new CPU set that contains all elements from this
   135  // set that match the supplied predicate, without mutating the source set.
   136  func (s CPUSet) Filter(predicate func(int) bool) CPUSet {
   137  	s2 := NewCPUSet()
   138  	for cpu := range s.elems {
   139  		if predicate(cpu) {
   140  			s2.Add(cpu)
   141  		}
   142  	}
   143  	return s2
   144  }
   145  
   146  // FilterNot returns a new CPU set that contains all elements from this
   147  // set that do not match the supplied predicate, without mutating the source set.
   148  func (s CPUSet) FilterNot(predicate func(int) bool) CPUSet {
   149  	s2 := NewCPUSet()
   150  	for cpu := range s.elems {
   151  		if !predicate(cpu) {
   152  			s2.Add(cpu)
   153  		}
   154  	}
   155  	return s2
   156  }
   157  
   158  // IsSubsetOf returns true if the supplied set contains all the elements
   159  func (s CPUSet) IsSubsetOf(s2 CPUSet) bool {
   160  	result := true
   161  	for cpu := range s.elems {
   162  		if !s2.Contains(cpu) {
   163  			result = false
   164  			break
   165  		}
   166  	}
   167  	return result
   168  }
   169  
   170  // Union returns a new CPU set that contains all elements from this
   171  // set and all elements from the supplied set, without mutating either source set.
   172  func (s CPUSet) Union(s2 CPUSet) CPUSet {
   173  	s3 := NewCPUSet()
   174  	for cpu := range s.elems {
   175  		s3.Add(cpu)
   176  	}
   177  	for cpu := range s2.elems {
   178  		s3.Add(cpu)
   179  	}
   180  	return s3
   181  }
   182  
   183  // UnionAll returns a new CPU set that contains all elements from this
   184  // set and all elements from the supplied sets, without mutating either source set.
   185  func (s CPUSet) UnionAll(s2 []CPUSet) CPUSet {
   186  	s3 := NewCPUSet()
   187  	for cpu := range s.elems {
   188  		s3.Add(cpu)
   189  	}
   190  	for _, cs := range s2 {
   191  		for cpu := range cs.elems {
   192  			s3.Add(cpu)
   193  		}
   194  	}
   195  	return s3
   196  }
   197  
   198  // Intersection returns a new CPU set that contains all of the elements
   199  // that are present in both this set and the supplied set, without mutating
   200  // either source set.
   201  func (s CPUSet) Intersection(s2 CPUSet) CPUSet {
   202  	return s.Filter(func(cpu int) bool { return s2.Contains(cpu) })
   203  }
   204  
   205  // Difference returns a new CPU set that contains all of the elements that
   206  // are present in this set and not the supplied set, without mutating either
   207  // source set.
   208  func (s CPUSet) Difference(s2 CPUSet) CPUSet {
   209  	return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) })
   210  }
   211  
   212  // ToSliceInt returns an ordered slice of int that contains
   213  // all elements from this set
   214  func (s CPUSet) ToSliceInt() []int {
   215  	result := []int{}
   216  	for cpu := range s.elems {
   217  		result = append(result, cpu)
   218  	}
   219  	sort.Ints(result)
   220  	return result
   221  }
   222  
   223  // ToSliceInt64 returns an ordered slice of int64 that contains
   224  // all elements from this set
   225  func (s CPUSet) ToSliceInt64() []int64 {
   226  	result := []int64{}
   227  	for cpu := range s.elems {
   228  		result = append(result, int64(cpu))
   229  	}
   230  	sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
   231  	return result
   232  }
   233  
   234  // ToSliceUInt64 returns an ordered slice of uint64 that contains
   235  // all elements from this set
   236  func (s CPUSet) ToSliceUInt64() []uint64 {
   237  	result := []uint64{}
   238  	for cpu := range s.elems {
   239  		result = append(result, uint64(cpu))
   240  	}
   241  	sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
   242  	return result
   243  }
   244  
   245  // ToSliceNoSortInt returns an ordered slice of int that contains
   246  // all elements from this set
   247  func (s CPUSet) ToSliceNoSortInt() []int {
   248  	result := []int{}
   249  	for cpu := range s.elems {
   250  		result = append(result, cpu)
   251  	}
   252  	return result
   253  }
   254  
   255  // ToSliceNoSortInt64 returns an ordered slice of int64 that contains
   256  // all elements from this set
   257  func (s CPUSet) ToSliceNoSortInt64() []int64 {
   258  	result := []int64{}
   259  	for cpu := range s.elems {
   260  		result = append(result, int64(cpu))
   261  	}
   262  	return result
   263  }
   264  
   265  // ToSliceNoSortUInt64 returns an ordered slice of uint64 that contains
   266  // all elements from this set
   267  func (s CPUSet) ToSliceNoSortUInt64() []uint64 {
   268  	result := []uint64{}
   269  	for cpu := range s.elems {
   270  		result = append(result, uint64(cpu))
   271  	}
   272  	return result
   273  }
   274  
   275  // String returns a new string representation of the elements in this CPU set
   276  // in canonical linux CPU list format.
   277  //
   278  // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
   279  func (s CPUSet) String() string {
   280  	if s.IsEmpty() {
   281  		return ""
   282  	}
   283  
   284  	elems := s.ToSliceInt()
   285  	type rng struct {
   286  		start int
   287  		end   int
   288  	}
   289  
   290  	ranges := []rng{{elems[0], elems[0]}}
   291  	for i := 1; i < len(elems); i++ {
   292  		lastRange := &ranges[len(ranges)-1]
   293  		// if this element is adjacent to the high end of the last range
   294  		if elems[i] == lastRange.end+1 {
   295  			// then extend the last range to include this element
   296  			lastRange.end = elems[i]
   297  			continue
   298  		}
   299  		// otherwise, start a new range beginning with this element
   300  		ranges = append(ranges, rng{elems[i], elems[i]})
   301  	}
   302  
   303  	// construct string from ranges
   304  	var result bytes.Buffer
   305  	for _, r := range ranges {
   306  		if r.start == r.end {
   307  			result.WriteString(strconv.Itoa(r.start))
   308  		} else {
   309  			result.WriteString(fmt.Sprintf("%d-%d", r.start, r.end))
   310  		}
   311  		result.WriteString(",")
   312  	}
   313  	return strings.TrimRight(result.String(), ",")
   314  }
   315  
   316  // MustParse CPUSet constructs a new CPU set from a Linux CPU list formatted
   317  // string. Unlike Parse, it does not return an error but rather panics if the
   318  // input cannot be used to construct a CPU set.
   319  func MustParse(s string) CPUSet {
   320  	res, err := Parse(s)
   321  	if err != nil {
   322  		klog.Fatalf("unable to parse [%s] as CPUSet: %v", s, err)
   323  	}
   324  	return res
   325  }
   326  
   327  // Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string.
   328  //
   329  // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
   330  func Parse(s string) (CPUSet, error) {
   331  	s2 := NewCPUSet()
   332  
   333  	// Handle empty string.
   334  	if s == "" {
   335  		return s2, nil
   336  	}
   337  
   338  	// Split CPU list string:
   339  	// "0-5,34,46-48 => ["0-5", "34", "46-48"]
   340  	ranges := strings.Split(s, ",")
   341  
   342  	for _, r := range ranges {
   343  		boundaries := strings.Split(r, "-")
   344  		if len(boundaries) == 1 {
   345  			// Handle ranges that consist of only one element like "34".
   346  			elem, err := strconv.Atoi(boundaries[0])
   347  			if err != nil {
   348  				return NewCPUSet(), err
   349  			}
   350  			s2.Add(elem)
   351  		} else if len(boundaries) == 2 {
   352  			// Handle multi-element ranges like "0-5".
   353  			start, err := strconv.Atoi(boundaries[0])
   354  			if err != nil {
   355  				return NewCPUSet(), err
   356  			}
   357  			end, err := strconv.Atoi(boundaries[1])
   358  			if err != nil {
   359  				return NewCPUSet(), err
   360  			}
   361  			// Add all elements to the result.
   362  			// e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48].
   363  			for e := start; e <= end; e++ {
   364  				s2.Add(e)
   365  			}
   366  		}
   367  	}
   368  	return s2, nil
   369  }