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 }