From b85b369035ad2773b098aeb06551c106905965ca Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Tue, 22 Aug 2017 10:44:01 -0700 Subject: [PATCH 01/41] Add cpuset helper library. --- BUILD | 36 ++++++ OWNERS | 5 + cpuset.go | 260 ++++++++++++++++++++++++++++++++++++++ cpuset_test.go | 330 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 631 insertions(+) create mode 100644 BUILD create mode 100644 OWNERS create mode 100644 cpuset.go create mode 100644 cpuset_test.go diff --git a/BUILD b/BUILD new file mode 100644 index 00000000..84f1beeb --- /dev/null +++ b/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["cpuset_test.go"], + library = ":go_default_library", + tags = ["automanaged"], +) + +go_library( + name = "go_default_library", + srcs = ["cpuset.go"], + tags = ["automanaged"], + deps = ["//vendor/github.com/golang/glog:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/OWNERS b/OWNERS new file mode 100644 index 00000000..02f72bf6 --- /dev/null +++ b/OWNERS @@ -0,0 +1,5 @@ +approvers: +- derekwaynecarr +- vishh +- ConnorDoyle +- sjenning diff --git a/cpuset.go b/cpuset.go new file mode 100644 index 00000000..7021f80d --- /dev/null +++ b/cpuset.go @@ -0,0 +1,260 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cpuset + +import ( + "bytes" + "fmt" + "github.com/golang/glog" + "reflect" + "sort" + "strconv" + "strings" +) + +// CPUSet is a set-like data structure for CPU IDs. +type CPUSet map[int]struct{} + +// NewCPUSet return CPUSet based on provided cpu id's +func NewCPUSet(cpus ...int) CPUSet { + res := CPUSet{} + for _, c := range cpus { + res.Add(c) + } + return res +} + +// Size returns the number of elements in this set. +func (s CPUSet) Size() int { + return len(s) +} + +// IsEmpty returns true if there are zero elements in this set. +func (s CPUSet) IsEmpty() bool { + return s.Size() == 0 +} + +// Contains returns true if the supplied element is present in this set. +func (s CPUSet) Contains(cpu int) bool { + _, found := s[cpu] + return found +} + +// Add mutates this set to contain the supplied elements. +func (s CPUSet) Add(cpus ...int) { + for _, cpu := range cpus { + s[cpu] = struct{}{} + } +} + +// Remove mutates this set to not contain the supplied elements, if they +// exists. +func (s CPUSet) Remove(cpus ...int) { + for _, cpu := range cpus { + delete(s, cpu) + } +} + +// Equals returns true if the supplied set contains exactly the same elements +// as this set (s IsSubsetOf s2 and s2 IsSubsetOf s). +func (s CPUSet) Equals(s2 CPUSet) bool { + return reflect.DeepEqual(s, s2) +} + +// Filter returns a new CPU set that contains all of the elements from this +// set that match the supplied predicate, without mutating the source set. +func (s CPUSet) Filter(predicate func(int) bool) CPUSet { + result := NewCPUSet() + for cpu := range s { + if predicate(cpu) { + result.Add(cpu) + } + } + return result +} + +// FilterNot returns a new CPU set that contains all of the elements from this +// set that do not match the supplied predicate, without mutating the source +// set. +func (s CPUSet) FilterNot(predicate func(int) bool) CPUSet { + result := NewCPUSet() + for cpu := range s { + if !predicate(cpu) { + result.Add(cpu) + } + } + return result +} + +// IsSubsetOf returns true if the supplied set contains all the elements +func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { + result := true + for cpu := range s { + if !s2.Contains(cpu) { + result = false + break + } + } + return result +} + +// Union returns a new CPU set that contains all of the elements from this +// set and all of the elements from the supplied set, without mutating +// either source set. +func (s CPUSet) Union(s2 CPUSet) CPUSet { + result := NewCPUSet() + for cpu := range s { + result.Add(cpu) + } + for cpu := range s2 { + result.Add(cpu) + } + return result +} + +// Intersection returns a new CPU set that contains all of the elements +// that are present in both this set and the supplied set, without mutating +// either source set. +func (s CPUSet) Intersection(s2 CPUSet) CPUSet { + return s.Filter(func(cpu int) bool { return s2.Contains(cpu) }) +} + +// Difference returns a new CPU set that contains all of the elements that +// are present in this set and not the supplied set, without mutating either +// source set. +func (s CPUSet) Difference(s2 CPUSet) CPUSet { + return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) }) +} + +// AsSlice returns a slice of integers that contains all elements from +// this set. +func (s CPUSet) AsSlice() []int { + result := []int{} + for cpu := range s { + result = append(result, cpu) + } + sort.Ints(result) + return result +} + +// String returns a new string representation of the elements in this CPU set +// in canonical linux CPU list format. +// +// See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS +func (s CPUSet) String() string { + if s.IsEmpty() { + return "" + } + + elems := s.AsSlice() + sort.Ints(elems) + + type rng struct { + start int + end int + } + + ranges := []rng{{elems[0], elems[0]}} + + for i := 1; i < len(elems); i++ { + lastRange := &ranges[len(ranges)-1] + // if this element is adjacent to the high end of the last range + if elems[i] == lastRange.end+1 { + // then extend the last range to include this element + lastRange.end = elems[i] + continue + } + // otherwise, start a new range beginning with this element + ranges = append(ranges, rng{elems[i], elems[i]}) + } + + // construct string from ranges + var result bytes.Buffer + for _, r := range ranges { + if r.start == r.end { + result.WriteString(strconv.Itoa(r.start)) + } else { + result.WriteString(fmt.Sprintf("%d-%d", r.start, r.end)) + } + result.WriteString(",") + } + return strings.TrimRight(result.String(), ",") +} + +// MustParse CPUSet constructs a new CPU set from a Linux CPU list formatted +// string. Unlike Parse, it does not return an error but rather panics if the +// input cannot be used to construct a CPU set. +func MustParse(s string) CPUSet { + res, err := Parse(s) + if err != nil { + glog.Fatalf("unable to parse [%s] as CPUSet: %v", s, err) + } + return res +} + +// Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string. +// +// See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS +func Parse(s string) (CPUSet, error) { + result := NewCPUSet() + + // Handle empty string. + if s == "" { + return result, nil + } + + // Split CPU list string: + // "0-5,34,46-48 => ["0-5", "34", "46-48"] + ranges := strings.Split(s, ",") + + for _, r := range ranges { + boundaries := strings.Split(r, "-") + if len(boundaries) == 1 { + // Handle ranges that consist of only one element like "34". + elem, err := strconv.Atoi(boundaries[0]) + if err != nil { + return nil, err + } + result.Add(elem) + } else if len(boundaries) == 2 { + // Handle multi-element ranges like "0-5". + start, err := strconv.Atoi(boundaries[0]) + if err != nil { + return nil, err + } + end, err := strconv.Atoi(boundaries[1]) + if err != nil { + return nil, err + } + // Add all elements to the result. + // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48]. + for e := start; e <= end; e++ { + result.Add(e) + } + } + } + return result, nil +} + +// Clone returns a copy of this CPU set. +func (s CPUSet) Clone() CPUSet { + res := NewCPUSet() + for k, v := range s { + res[k] = v + } + return res +} diff --git a/cpuset_test.go b/cpuset_test.go new file mode 100644 index 00000000..f17530c5 --- /dev/null +++ b/cpuset_test.go @@ -0,0 +1,330 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cpuset + +import ( + "reflect" + "testing" +) + +func TestCPUSetSize(t *testing.T) { + testCases := []struct { + cpuset CPUSet + expected int + }{ + {NewCPUSet(), 0}, + {NewCPUSet(5), 1}, + {NewCPUSet(1, 2, 3, 4, 5), 5}, + } + + for _, c := range testCases { + actual := c.cpuset.Size() + if actual != c.expected { + t.Fatalf("expected: %d, actual: %d, cpuset: [%v]", c.expected, actual, c.cpuset) + } + } +} + +func TestCPUSetIsEmpty(t *testing.T) { + testCases := []struct { + cpuset CPUSet + expected bool + }{ + {NewCPUSet(), true}, + {NewCPUSet(5), false}, + {NewCPUSet(1, 2, 3, 4, 5), false}, + } + + for _, c := range testCases { + actual := c.cpuset.IsEmpty() + if actual != c.expected { + t.Fatalf("expected: %t, IsEmpty() returned: %t, cpuset: [%v]", c.expected, actual, c.cpuset) + } + } +} + +func TestCPUSetContains(t *testing.T) { + testCases := []struct { + cpuset CPUSet + mustContain []int + mustNotContain []int + }{ + {NewCPUSet(), []int{}, []int{1, 2, 3, 4, 5}}, + {NewCPUSet(5), []int{5}, []int{1, 2, 3, 4}}, + {NewCPUSet(1, 2, 4, 5), []int{1, 2, 4, 5}, []int{0, 3, 6}}, + } + + for _, c := range testCases { + for _, elem := range c.mustContain { + if !c.cpuset.Contains(elem) { + t.Fatalf("expected cpuset to contain element %d: [%v]", elem, c.cpuset) + } + } + for _, elem := range c.mustNotContain { + if c.cpuset.Contains(elem) { + t.Fatalf("expected cpuset not to contain element %d: [%v]", elem, c.cpuset) + } + } + } +} + +func TestCPUSetAdd(t *testing.T) { + result := NewCPUSet() + for _, elem := range []int{1, 2, 3, 4, 5} { + result.Add(elem) + if !result.Contains(elem) { + t.Fatalf("expected cpuset to contain element %d: [%v]", elem, result) + } + } +} + +func TestCPUSetRemove(t *testing.T) { + result := NewCPUSet(1, 2, 3, 4, 5) + for _, elem := range []int{1, 2, 3, 4, 5} { + result.Remove(elem) + if result.Contains(elem) { + t.Fatalf("expected cpuset to not contain element %d: [%v]", elem, result) + } + } + if !result.IsEmpty() { + t.Fatalf("expected cpuset to be empty: [%v]", result) + } +} + +func TestCPUSetEqual(t *testing.T) { + shouldEqual := []struct { + s1 CPUSet + s2 CPUSet + }{ + {NewCPUSet(), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + } + + shouldNotEqual := []struct { + s1 CPUSet + s2 CPUSet + }{ + {NewCPUSet(), NewCPUSet(5)}, + {NewCPUSet(5), NewCPUSet()}, + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5)}, + } + + for _, c := range shouldEqual { + if !c.s1.Equals(c.s2) { + t.Fatalf("expected cpusets to be equal: s1: [%v], s2: [%v]", c.s1, c.s2) + } + } + for _, c := range shouldNotEqual { + if c.s1.Equals(c.s2) { + t.Fatalf("expected cpusets to not be equal: s1: [%v], s2: [%v]", c.s1, c.s2) + } + } +} + +func TestCPUSetIsSubsetOf(t *testing.T) { + shouldBeSubset := []struct { + s1 CPUSet + s2 CPUSet + }{ + // A set is a subset of itself + {NewCPUSet(), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + + // Empty set is a subset of every set + {NewCPUSet(), NewCPUSet(5)}, + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, + + {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(2, 3), NewCPUSet(1, 2, 3, 4, 5)}, + } + + shouldNotBeSubset := []struct { + s1 CPUSet + s2 CPUSet + }{} + + for _, c := range shouldBeSubset { + if !c.s1.IsSubsetOf(c.s2) { + t.Fatalf("expected s1 to be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) + } + } + for _, c := range shouldNotBeSubset { + if c.s1.IsSubsetOf(c.s2) { + t.Fatalf("expected s1 to not be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) + } + } +} + +func TestCPUSetUnion(t *testing.T) { + testCases := []struct { + s1 CPUSet + s2 CPUSet + expected CPUSet + }{ + {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + + {NewCPUSet(), NewCPUSet(5), NewCPUSet(5)}, + {NewCPUSet(5), NewCPUSet(), NewCPUSet(5)}, + {NewCPUSet(5), NewCPUSet(5), NewCPUSet(5)}, + + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + + {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, + + {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + } + + for _, c := range testCases { + result := c.s1.Union(c.s2) + if !result.Equals(c.expected) { + t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + } + } +} + +func TestCPUSetIntersection(t *testing.T) { + testCases := []struct { + s1 CPUSet + s2 CPUSet + expected CPUSet + }{ + {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + + {NewCPUSet(), NewCPUSet(5), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(5), NewCPUSet(5)}, + + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet()}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + + {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(5)}, + + {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet()}, + {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(3)}, + } + + for _, c := range testCases { + result := c.s1.Intersection(c.s2) + if !result.Equals(c.expected) { + t.Fatalf("expected the intersection of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + } + } +} + +func TestCPUSetDifference(t *testing.T) { + testCases := []struct { + s1 CPUSet + s2 CPUSet + expected CPUSet + }{ + {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + + {NewCPUSet(), NewCPUSet(5), NewCPUSet()}, + {NewCPUSet(5), NewCPUSet(), NewCPUSet(5)}, + {NewCPUSet(5), NewCPUSet(5), NewCPUSet()}, + + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + + {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(1, 2, 3, 4)}, + + {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet(1, 2)}, + {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(1, 2)}, + } + + for _, c := range testCases { + result := c.s1.Difference(c.s2) + if !result.Equals(c.expected) { + t.Fatalf("expected the difference of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + } + } +} + +func TestCPUSetAsSlice(t *testing.T) { + testCases := []struct { + set CPUSet + expected []int + }{ + {NewCPUSet(), []int{}}, + {NewCPUSet(5), []int{5}}, + {NewCPUSet(1, 2, 3, 4, 5), []int{1, 2, 3, 4, 5}}, + } + + for _, c := range testCases { + result := c.set.AsSlice() + if !reflect.DeepEqual(result, c.expected) { + t.Fatalf("expected set as slice to be [%v] (got [%v]), s: [%v]", c.expected, result, c.set) + } + } +} + +func TestCPUSetString(t *testing.T) { + testCases := []struct { + set CPUSet + expected string + }{ + {NewCPUSet(), ""}, + {NewCPUSet(5), "5"}, + {NewCPUSet(1, 2, 3, 4, 5), "1-5"}, + {NewCPUSet(1, 2, 3, 5, 6, 8), "1-3,5-6,8"}, + } + + for _, c := range testCases { + result := c.set.String() + if result != c.expected { + t.Fatalf("expected set as string to be %s (got \"%s\"), s: [%v]", c.expected, result, c.set) + } + } +} + +func TestParse(t *testing.T) { + testCases := []struct { + cpusetString string + expected CPUSet + }{ + {"", NewCPUSet()}, + {"5", NewCPUSet(5)}, + {"1,2,3,4,5", NewCPUSet(1, 2, 3, 4, 5)}, + {"1-5", NewCPUSet(1, 2, 3, 4, 5)}, + {"1-2,3-5", NewCPUSet(1, 2, 3, 4, 5)}, + } + + for _, c := range testCases { + result, err := Parse(c.cpusetString) + if err != nil { + t.Fatalf("expected error not to have occurred: %v", err) + } + if !result.Equals(c.expected) { + t.Fatalf("expected string \"%s\" to parse as [%v] (got [%v])", c.cpusetString, c.expected, result) + } + } +} From d1715d65b0db9d9fde81d5588081df9f8fbfc7de Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Tue, 22 Aug 2017 21:21:26 -0700 Subject: [PATCH 02/41] Renamed CPUSet.AsSlice() => CPUSet.ToSlice() --- cpuset.go | 6 +++--- cpuset_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpuset.go b/cpuset.go index 7021f80d..1a388c28 100644 --- a/cpuset.go +++ b/cpuset.go @@ -140,9 +140,9 @@ func (s CPUSet) Difference(s2 CPUSet) CPUSet { return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) }) } -// AsSlice returns a slice of integers that contains all elements from +// ToSlice returns a slice of integers that contains all elements from // this set. -func (s CPUSet) AsSlice() []int { +func (s CPUSet) ToSlice() []int { result := []int{} for cpu := range s { result = append(result, cpu) @@ -160,7 +160,7 @@ func (s CPUSet) String() string { return "" } - elems := s.AsSlice() + elems := s.ToSlice() sort.Ints(elems) type rng struct { diff --git a/cpuset_test.go b/cpuset_test.go index f17530c5..6652eb01 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -269,7 +269,7 @@ func TestCPUSetDifference(t *testing.T) { } } -func TestCPUSetAsSlice(t *testing.T) { +func TestCPUSetToSlice(t *testing.T) { testCases := []struct { set CPUSet expected []int @@ -280,7 +280,7 @@ func TestCPUSetAsSlice(t *testing.T) { } for _, c := range testCases { - result := c.set.AsSlice() + result := c.set.ToSlice() if !reflect.DeepEqual(result, c.expected) { t.Fatalf("expected set as slice to be [%v] (got [%v]), s: [%v]", c.expected, result, c.set) } From 4fa778d96b30b1a5b4ac332d65f7e66f9c853c33 Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Tue, 22 Aug 2017 21:22:37 -0700 Subject: [PATCH 03/41] Add CPUSetBuilder, make CPUSet immutable. --- cpuset.go | 126 ++++++++++++++++++++++++++++--------------------- cpuset_test.go | 40 +++++++--------- 2 files changed, 90 insertions(+), 76 deletions(-) diff --git a/cpuset.go b/cpuset.go index 1a388c28..ccf577d8 100644 --- a/cpuset.go +++ b/cpuset.go @@ -26,21 +26,57 @@ import ( "strings" ) -// CPUSet is a set-like data structure for CPU IDs. -type CPUSet map[int]struct{} +// Builder is a mutable builder for CPUSet. Functions that mutate instances +// of this type are not thread-safe. +type Builder struct { + result CPUSet + done bool +} + +// NewBuilder returns a mutable CPUSet builder. +func NewBuilder() Builder { + return Builder{ + result: CPUSet{ + elems: map[int]struct{}{}, + }, + } +} + +// Add adds the supplied elements to the result. Calling Add after calling +// Result has no effect. +func (b Builder) Add(elems ...int) { + if b.done { + return + } + for _, elem := range elems { + b.result.elems[elem] = struct{}{} + } +} + +// Result returns the result CPUSet containing all elements that were +// previously added to this builder. Subsequent calls to Add have no effect. +func (b Builder) Result() CPUSet { + b.done = true + return b.result +} + +// CPUSet is a thread-safe, immutable set-like data structure for CPU IDs. +type CPUSet struct { + elems map[int]struct{} +} -// NewCPUSet return CPUSet based on provided cpu id's +// NewCPUSet returns a new CPUSet containing the supplied elements. func NewCPUSet(cpus ...int) CPUSet { - res := CPUSet{} + b := NewBuilder() for _, c := range cpus { - res.Add(c) + b.Add(c) } - return res + return b.Result() } // Size returns the number of elements in this set. func (s CPUSet) Size() int { - return len(s) + return len(s.elems) } // IsEmpty returns true if there are zero elements in this set. @@ -50,60 +86,45 @@ func (s CPUSet) IsEmpty() bool { // Contains returns true if the supplied element is present in this set. func (s CPUSet) Contains(cpu int) bool { - _, found := s[cpu] + _, found := s.elems[cpu] return found } -// Add mutates this set to contain the supplied elements. -func (s CPUSet) Add(cpus ...int) { - for _, cpu := range cpus { - s[cpu] = struct{}{} - } -} - -// Remove mutates this set to not contain the supplied elements, if they -// exists. -func (s CPUSet) Remove(cpus ...int) { - for _, cpu := range cpus { - delete(s, cpu) - } -} - // Equals returns true if the supplied set contains exactly the same elements // as this set (s IsSubsetOf s2 and s2 IsSubsetOf s). func (s CPUSet) Equals(s2 CPUSet) bool { - return reflect.DeepEqual(s, s2) + return reflect.DeepEqual(s.elems, s2.elems) } // Filter returns a new CPU set that contains all of the elements from this // set that match the supplied predicate, without mutating the source set. func (s CPUSet) Filter(predicate func(int) bool) CPUSet { - result := NewCPUSet() - for cpu := range s { + b := NewBuilder() + for cpu := range s.elems { if predicate(cpu) { - result.Add(cpu) + b.Add(cpu) } } - return result + return b.Result() } // FilterNot returns a new CPU set that contains all of the elements from this // set that do not match the supplied predicate, without mutating the source // set. func (s CPUSet) FilterNot(predicate func(int) bool) CPUSet { - result := NewCPUSet() - for cpu := range s { + b := NewBuilder() + for cpu := range s.elems { if !predicate(cpu) { - result.Add(cpu) + b.Add(cpu) } } - return result + return b.Result() } // IsSubsetOf returns true if the supplied set contains all the elements func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { result := true - for cpu := range s { + for cpu := range s.elems { if !s2.Contains(cpu) { result = false break @@ -116,14 +137,14 @@ func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { // set and all of the elements from the supplied set, without mutating // either source set. func (s CPUSet) Union(s2 CPUSet) CPUSet { - result := NewCPUSet() - for cpu := range s { - result.Add(cpu) + b := NewBuilder() + for cpu := range s.elems { + b.Add(cpu) } - for cpu := range s2 { - result.Add(cpu) + for cpu := range s2.elems { + b.Add(cpu) } - return result + return b.Result() } // Intersection returns a new CPU set that contains all of the elements @@ -144,7 +165,7 @@ func (s CPUSet) Difference(s2 CPUSet) CPUSet { // this set. func (s CPUSet) ToSlice() []int { result := []int{} - for cpu := range s { + for cpu := range s.elems { result = append(result, cpu) } sort.Ints(result) @@ -161,7 +182,6 @@ func (s CPUSet) String() string { } elems := s.ToSlice() - sort.Ints(elems) type rng struct { start int @@ -210,11 +230,11 @@ func MustParse(s string) CPUSet { // // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS func Parse(s string) (CPUSet, error) { - result := NewCPUSet() + b := NewBuilder() // Handle empty string. if s == "" { - return result, nil + return b.Result(), nil } // Split CPU list string: @@ -227,34 +247,34 @@ func Parse(s string) (CPUSet, error) { // Handle ranges that consist of only one element like "34". elem, err := strconv.Atoi(boundaries[0]) if err != nil { - return nil, err + return NewCPUSet(), err } - result.Add(elem) + b.Add(elem) } else if len(boundaries) == 2 { // Handle multi-element ranges like "0-5". start, err := strconv.Atoi(boundaries[0]) if err != nil { - return nil, err + return NewCPUSet(), err } end, err := strconv.Atoi(boundaries[1]) if err != nil { - return nil, err + return NewCPUSet(), err } // Add all elements to the result. // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48]. for e := start; e <= end; e++ { - result.Add(e) + b.Add(e) } } } - return result, nil + return b.Result(), nil } // Clone returns a copy of this CPU set. func (s CPUSet) Clone() CPUSet { - res := NewCPUSet() - for k, v := range s { - res[k] = v + b := NewBuilder() + for elem := range s.elems { + b.Add(elem) } - return res + return b.Result() } diff --git a/cpuset_test.go b/cpuset_test.go index 6652eb01..e2d2410b 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -21,6 +21,23 @@ import ( "testing" ) +func TestCPUSetBuilder(t *testing.T) { + b := NewBuilder() + elems := []int{1, 2, 3, 4, 5} + for _, elem := range elems { + b.Add(elem) + } + result := b.Result() + for _, elem := range elems { + if !result.Contains(elem) { + t.Fatalf("expected cpuset to contain element %d: [%v]", elem, result) + } + } + if len(elems) != result.Size() { + t.Fatalf("expected cpuset %s to have the same size as %v", result, elems) + } +} + func TestCPUSetSize(t *testing.T) { testCases := []struct { cpuset CPUSet @@ -82,29 +99,6 @@ func TestCPUSetContains(t *testing.T) { } } -func TestCPUSetAdd(t *testing.T) { - result := NewCPUSet() - for _, elem := range []int{1, 2, 3, 4, 5} { - result.Add(elem) - if !result.Contains(elem) { - t.Fatalf("expected cpuset to contain element %d: [%v]", elem, result) - } - } -} - -func TestCPUSetRemove(t *testing.T) { - result := NewCPUSet(1, 2, 3, 4, 5) - for _, elem := range []int{1, 2, 3, 4, 5} { - result.Remove(elem) - if result.Contains(elem) { - t.Fatalf("expected cpuset to not contain element %d: [%v]", elem, result) - } - } - if !result.IsEmpty() { - t.Fatalf("expected cpuset to be empty: [%v]", result) - } -} - func TestCPUSetEqual(t *testing.T) { shouldEqual := []struct { s1 CPUSet From ddade34edf02c084f021ca054258e3b6f24f2626 Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Mon, 28 Aug 2017 15:02:01 -0700 Subject: [PATCH 04/41] CPU Manager initialization and lifecycle calls. --- BUILD | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/BUILD b/BUILD index 84f1beeb..24ee7753 100644 --- a/BUILD +++ b/BUILD @@ -1,25 +1,16 @@ -package(default_visibility = ["//visibility:public"]) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") -licenses(["notice"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", +go_library( + name = "go_default_library", + srcs = ["cpuset.go"], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/golang/glog:go_default_library"], ) go_test( name = "go_default_test", srcs = ["cpuset_test.go"], library = ":go_default_library", - tags = ["automanaged"], -) - -go_library( - name = "go_default_library", - srcs = ["cpuset.go"], - tags = ["automanaged"], - deps = ["//vendor/github.com/golang/glog:go_default_library"], ) filegroup( @@ -33,4 +24,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) From f1d8919e563ec82435d8a00478dc05b04a10664d Mon Sep 17 00:00:00 2001 From: Shyam JVS Date: Fri, 1 Sep 2017 18:17:36 +0200 Subject: [PATCH 05/41] Revert "CPU manager wiring and `none` policy" --- BUILD | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/BUILD b/BUILD index 24ee7753..84f1beeb 100644 --- a/BUILD +++ b/BUILD @@ -1,16 +1,25 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +package(default_visibility = ["//visibility:public"]) -go_library( - name = "go_default_library", - srcs = ["cpuset.go"], - visibility = ["//visibility:public"], - deps = ["//vendor/github.com/golang/glog:go_default_library"], +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", ) go_test( name = "go_default_test", srcs = ["cpuset_test.go"], library = ":go_default_library", + tags = ["automanaged"], +) + +go_library( + name = "go_default_library", + srcs = ["cpuset.go"], + tags = ["automanaged"], + deps = ["//vendor/github.com/golang/glog:go_default_library"], ) filegroup( @@ -24,5 +33,4 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], - visibility = ["//visibility:public"], ) From 29447adc484745038bd0e427c18ee28cfa90be67 Mon Sep 17 00:00:00 2001 From: Connor Doyle Date: Fri, 1 Sep 2017 10:46:39 -0700 Subject: [PATCH 06/41] Un-revert "CPU manager wiring and `none` policy" This reverts commit 8d2832021ad049eee6f184315a10f3abe18036f9. --- BUILD | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/BUILD b/BUILD index 84f1beeb..24ee7753 100644 --- a/BUILD +++ b/BUILD @@ -1,25 +1,16 @@ -package(default_visibility = ["//visibility:public"]) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") -licenses(["notice"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", +go_library( + name = "go_default_library", + srcs = ["cpuset.go"], + visibility = ["//visibility:public"], + deps = ["//vendor/github.com/golang/glog:go_default_library"], ) go_test( name = "go_default_test", srcs = ["cpuset_test.go"], library = ":go_default_library", - tags = ["automanaged"], -) - -go_library( - name = "go_default_library", - srcs = ["cpuset.go"], - tags = ["automanaged"], - deps = ["//vendor/github.com/golang/glog:go_default_library"], ) filegroup( @@ -33,4 +24,5 @@ filegroup( name = "all-srcs", srcs = [":package-srcs"], tags = ["automanaged"], + visibility = ["//visibility:public"], ) From ec56fa4c1508d3db31a2a02c9f1c40dc268bd42f Mon Sep 17 00:00:00 2001 From: Jeff Grafton Date: Thu, 12 Oct 2017 13:52:10 -0700 Subject: [PATCH 07/41] update BUILD files --- BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BUILD b/BUILD index 24ee7753..7ed863a5 100644 --- a/BUILD +++ b/BUILD @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = ["cpuset.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", visibility = ["//visibility:public"], deps = ["//vendor/github.com/golang/glog:go_default_library"], ) @@ -10,6 +11,7 @@ go_library( go_test( name = "go_default_test", srcs = ["cpuset_test.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", library = ":go_default_library", ) From cff40fdfd80f34d43c99d8cf13a9db3308afebca Mon Sep 17 00:00:00 2001 From: Jeff Grafton Date: Sat, 23 Dec 2017 13:06:26 -0800 Subject: [PATCH 08/41] Autogenerate BUILD files --- BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD b/BUILD index 7ed863a5..eadcb49b 100644 --- a/BUILD +++ b/BUILD @@ -11,8 +11,8 @@ go_library( go_test( name = "go_default_test", srcs = ["cpuset_test.go"], + embed = [":go_default_library"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", - library = ":go_default_library", ) filegroup( From 7eb02a21d345f3b7f99c9a895160fb526e0626b2 Mon Sep 17 00:00:00 2001 From: Jeff Grafton Date: Fri, 16 Feb 2018 13:43:01 -0800 Subject: [PATCH 09/41] Autogenerated: hack/update-bazel.sh --- BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/BUILD b/BUILD index eadcb49b..89126d68 100644 --- a/BUILD +++ b/BUILD @@ -12,7 +12,6 @@ go_test( name = "go_default_test", srcs = ["cpuset_test.go"], embed = [":go_default_library"], - importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", ) filegroup( From 7e4044532d302510344882645680ebbacdbff488 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 9 Nov 2018 13:49:10 -0500 Subject: [PATCH 10/41] Move from glog to klog - Move from the old github.com/golang/glog to k8s.io/klog - klog as explicit InitFlags() so we add them as necessary - we update the other repositories that we vendor that made a similar change from glog to klog * github.com/kubernetes/repo-infra * k8s.io/gengo/ * k8s.io/kube-openapi/ * github.com/google/cadvisor - Entirely remove all references to glog - Fix some tests by explicit InitFlags in their init() methods Change-Id: I92db545ff36fcec83afe98f550c9e630098b3135 --- BUILD | 2 +- cpuset.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD b/BUILD index 89126d68..9b81edc1 100644 --- a/BUILD +++ b/BUILD @@ -5,7 +5,7 @@ go_library( srcs = ["cpuset.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", visibility = ["//visibility:public"], - deps = ["//vendor/github.com/golang/glog:go_default_library"], + deps = ["//vendor/k8s.io/klog:go_default_library"], ) go_test( diff --git a/cpuset.go b/cpuset.go index ccf577d8..d87efc78 100644 --- a/cpuset.go +++ b/cpuset.go @@ -19,7 +19,7 @@ package cpuset import ( "bytes" "fmt" - "github.com/golang/glog" + "k8s.io/klog" "reflect" "sort" "strconv" @@ -221,7 +221,7 @@ func (s CPUSet) String() string { func MustParse(s string) CPUSet { res, err := Parse(s) if err != nil { - glog.Fatalf("unable to parse [%s] as CPUSet: %v", s, err) + klog.Fatalf("unable to parse [%s] as CPUSet: %v", s, err) } return res } From 4b7ce99104c751ad98e37b129cac915ca63c2a73 Mon Sep 17 00:00:00 2001 From: Roy Lenferink Date: Wed, 30 Jan 2019 20:05:00 +0100 Subject: [PATCH 11/41] Updated OWNERS files to include link to docs --- OWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS b/OWNERS index 02f72bf6..e6f9bfda 100644 --- a/OWNERS +++ b/OWNERS @@ -1,3 +1,5 @@ +# See the OWNERS docs at https://go.k8s.io/owners + approvers: - derekwaynecarr - vishh From 698abcb0e7107dba44e849f79fef4d3fc30eb603 Mon Sep 17 00:00:00 2001 From: Ted Yu Date: Fri, 3 May 2019 14:07:44 -0700 Subject: [PATCH 12/41] Obtain unsorted slice in cpuAccumulator#freeCores --- cpuset.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cpuset.go b/cpuset.go index d87efc78..e49a54d2 100644 --- a/cpuset.go +++ b/cpuset.go @@ -172,6 +172,16 @@ func (s CPUSet) ToSlice() []int { return result } +// ToSliceNoSort returns a slice of integers that contains all elements from +// this set. +func (s CPUSet) ToSliceNoSort() []int { + result := []int{} + for cpu := range s.elems { + result = append(result, cpu) + } + return result +} + // String returns a new string representation of the elements in this CPU set // in canonical linux CPU list format. // From c6ec72811c9d37b23cfdb0b5fcd4cf40418a6c08 Mon Sep 17 00:00:00 2001 From: Ted Yu Date: Fri, 3 May 2019 14:40:31 -0700 Subject: [PATCH 13/41] Union all CPUSets in one round --- cpuset.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cpuset.go b/cpuset.go index d87efc78..56a99659 100644 --- a/cpuset.go +++ b/cpuset.go @@ -147,6 +147,22 @@ func (s CPUSet) Union(s2 CPUSet) CPUSet { return b.Result() } +// UnionAll returns a new CPU set that contains all of the elements from this +// set and all of the elements from the supplied sets, without mutating +// either source set. +func (s CPUSet) UnionAll(s2 []CPUSet) CPUSet { + b := NewBuilder() + for cpu := range s.elems { + b.Add(cpu) + } + for _, cs := range s2 { + for cpu := range cs.elems { + b.Add(cpu) + } + } + return b.Result() +} + // Intersection returns a new CPU set that contains all of the elements // that are present in both this set and the supplied set, without mutating // either source set. From 784b297915854e2e2153ba8f597263d4b21c1750 Mon Sep 17 00:00:00 2001 From: Ted Yu Date: Thu, 16 May 2019 12:13:31 -0700 Subject: [PATCH 14/41] Add test for CPUSet#UnionAll Signed-off-by: Ted Yu --- cpuset_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cpuset_test.go b/cpuset_test.go index e2d2410b..c77ab8b7 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -170,6 +170,28 @@ func TestCPUSetIsSubsetOf(t *testing.T) { } } +func TestCPUSetUnionAll(t *testing.T) { + testCases := []struct { + s1 CPUSet + s2 CPUSet + s3 CPUSet + expected CPUSet + }{ + {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(4), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 5), NewCPUSet(1, 2, 3, 4, 5)}, + } + for _, c := range testCases { + s := []CPUSet{} + s = append(s, c.s2) + s = append(s, c.s3) + result := c.s1.UnionAll(s) + if !result.Equals(c.expected) { + t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + } + } +} + func TestCPUSetUnion(t *testing.T) { testCases := []struct { s1 CPUSet From 095fd5a55d319420241125079e99439b04482c76 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 17 Apr 2020 15:25:06 -0400 Subject: [PATCH 15/41] switch over k/k to use klog v2 Signed-off-by: Davanum Srinivas --- cpuset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpuset.go b/cpuset.go index 45df6186..ae2062bd 100644 --- a/cpuset.go +++ b/cpuset.go @@ -19,7 +19,7 @@ package cpuset import ( "bytes" "fmt" - "k8s.io/klog" + "k8s.io/klog/v2" "reflect" "sort" "strconv" From 6a3eb90d8ad699a07092a80ba144307117ce8691 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Thu, 14 May 2020 19:00:57 -0400 Subject: [PATCH 16/41] Run hack/update-vendor.sh Signed-off-by: Davanum Srinivas --- BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD b/BUILD index 9b81edc1..d2e3ef73 100644 --- a/BUILD +++ b/BUILD @@ -5,7 +5,7 @@ go_library( srcs = ["cpuset.go"], importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", visibility = ["//visibility:public"], - deps = ["//vendor/k8s.io/klog:go_default_library"], + deps = ["//vendor/k8s.io/klog/v2:go_default_library"], ) go_test( From a30212fa21c12083bedf9a387ce52bf500913689 Mon Sep 17 00:00:00 2001 From: Seth Jennings Date: Tue, 16 Jun 2020 16:22:02 -0500 Subject: [PATCH 17/41] add sjenning as kubelet approver --- OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/OWNERS b/OWNERS index e6f9bfda..fe38ab54 100644 --- a/OWNERS +++ b/OWNERS @@ -4,4 +4,3 @@ approvers: - derekwaynecarr - vishh - ConnorDoyle -- sjenning From 3daa5f4d280002dd49f77beb161019ebe9f20f1e Mon Sep 17 00:00:00 2001 From: Artyom Lukianov Date: Mon, 18 Jan 2021 19:59:23 +0200 Subject: [PATCH 18/41] Provide additional methods under the CPUSet - ToSliceInt64 returns sorted slice of cores IDs in int64 format - ToSliceNoSortInt64 returns slice of cores IDs in int64 format Signed-off-by: Artyom Lukianov --- cpuset.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/cpuset.go b/cpuset.go index ae2062bd..ad7ea27a 100644 --- a/cpuset.go +++ b/cpuset.go @@ -19,11 +19,12 @@ package cpuset import ( "bytes" "fmt" - "k8s.io/klog/v2" "reflect" "sort" "strconv" "strings" + + "k8s.io/klog/v2" ) // Builder is a mutable builder for CPUSet. Functions that mutate instances @@ -198,6 +199,27 @@ func (s CPUSet) ToSliceNoSort() []int { return result } +// ToSliceInt64 returns an ordered slice of int64 that contains all elements from +// this set +func (s CPUSet) ToSliceInt64() []int64 { + var result []int64 + for cpu := range s.elems { + result = append(result, int64(cpu)) + } + sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) + return result +} + +// ToSliceNoSortInt64 returns a slice of int64 that contains all elements from +// this set. +func (s CPUSet) ToSliceNoSortInt64() []int64 { + var result []int64 + for cpu := range s.elems { + result = append(result, int64(cpu)) + } + return result +} + // String returns a new string representation of the elements in this CPU set // in canonical linux CPU list format. // From 8ad36693ba5d6c080258fe6015dd247e991af6c1 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Sun, 28 Feb 2021 14:17:42 -0800 Subject: [PATCH 19/41] hack/update-bazel.sh --- BUILD | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 BUILD diff --git a/BUILD b/BUILD deleted file mode 100644 index d2e3ef73..00000000 --- a/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["cpuset.go"], - importpath = "k8s.io/kubernetes/pkg/kubelet/cm/cpuset", - visibility = ["//visibility:public"], - deps = ["//vendor/k8s.io/klog/v2:go_default_library"], -) - -go_test( - name = "go_default_test", - srcs = ["cpuset_test.go"], - embed = [":go_default_library"], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) From c292b987225e0fd4002ef0a6550a7b74a1bf1d9f Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Wed, 14 Oct 2020 19:12:30 +0200 Subject: [PATCH 20/41] e2e: node: add tests for GetAllocatableResources Add e2e tests for the new GetAllocatableResources API. The tests are added in the `podresources_test` suite created previously in this series. Signed-off-by: Francesco Romani --- cpuset.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cpuset.go b/cpuset.go index ad7ea27a..7d892f85 100644 --- a/cpuset.go +++ b/cpuset.go @@ -75,6 +75,15 @@ func NewCPUSet(cpus ...int) CPUSet { return b.Result() } +// NewCPUSet returns a new CPUSet containing the supplied elements, as slice of int64. +func NewCPUSetInt64(cpus ...int64) CPUSet { + b := NewBuilder() + for _, c := range cpus { + b.Add(int(c)) + } + return b.Result() +} + // Size returns the number of elements in this set. func (s CPUSet) Size() int { return len(s.elems) From d75b841dcc6eb3e03836940dc653356e1e7050fd Mon Sep 17 00:00:00 2001 From: Utsav Oza Date: Tue, 9 Mar 2021 17:36:20 +0530 Subject: [PATCH 21/41] Migrate pkg/kubelet/cm/ top level files to structured logging --- cpuset.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpuset.go b/cpuset.go index ad7ea27a..2d48f1b2 100644 --- a/cpuset.go +++ b/cpuset.go @@ -19,6 +19,7 @@ package cpuset import ( "bytes" "fmt" + "os" "reflect" "sort" "strconv" @@ -269,7 +270,8 @@ func (s CPUSet) String() string { func MustParse(s string) CPUSet { res, err := Parse(s) if err != nil { - klog.Fatalf("unable to parse [%s] as CPUSet: %v", s, err) + klog.ErrorS(err, "Failed to parse input as CPUSet", "input", s) + os.Exit(1) } return res } From 0e7c4f79bdc2dc67805e525c699532803877264b Mon Sep 17 00:00:00 2001 From: Jim Ramsay Date: Thu, 25 Mar 2021 15:03:08 -0400 Subject: [PATCH 22/41] cpuset.Parse: Fix edge cases and add negative tests The cpuset.Parse function missed a couple bad input cases, specifically "1--3" and "10-6". These were silently ignored when they should instead be flagged as invalid. This now catches these cases and expands the unit tests for cpuset to cover them (and other negative test cases as well). Signed-off-by: Jim Ramsay --- cpuset.go | 7 ++++++- cpuset_test.go | 23 +++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cpuset.go b/cpuset.go index de72ba25..24330e5a 100644 --- a/cpuset.go +++ b/cpuset.go @@ -301,7 +301,7 @@ func Parse(s string) (CPUSet, error) { ranges := strings.Split(s, ",") for _, r := range ranges { - boundaries := strings.Split(r, "-") + boundaries := strings.SplitN(r, "-", 2) if len(boundaries) == 1 { // Handle ranges that consist of only one element like "34". elem, err := strconv.Atoi(boundaries[0]) @@ -319,6 +319,11 @@ func Parse(s string) (CPUSet, error) { if err != nil { return NewCPUSet(), err } + if start > end { + return NewCPUSet(), fmt.Errorf("invalid range %q (%d >= %d)", r, start, end) + } + // start == end is acceptable (1-1 -> 1) + // Add all elements to the result. // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48]. for e := start; e <= end; e++ { diff --git a/cpuset_test.go b/cpuset_test.go index c77ab8b7..d79748df 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -323,7 +323,7 @@ func TestCPUSetString(t *testing.T) { } func TestParse(t *testing.T) { - testCases := []struct { + positiveTestCases := []struct { cpusetString string expected CPUSet }{ @@ -332,9 +332,12 @@ func TestParse(t *testing.T) { {"1,2,3,4,5", NewCPUSet(1, 2, 3, 4, 5)}, {"1-5", NewCPUSet(1, 2, 3, 4, 5)}, {"1-2,3-5", NewCPUSet(1, 2, 3, 4, 5)}, + {"5,4,3,2,1", NewCPUSet(1, 2, 3, 4, 5)}, // Range ordering + {"3-6,1-5", NewCPUSet(1, 2, 3, 4, 5, 6)}, // Overlapping ranges + {"3-3,5-5", NewCPUSet(3, 5)}, // Very short ranges } - for _, c := range testCases { + for _, c := range positiveTestCases { result, err := Parse(c.cpusetString) if err != nil { t.Fatalf("expected error not to have occurred: %v", err) @@ -343,4 +346,20 @@ func TestParse(t *testing.T) { t.Fatalf("expected string \"%s\" to parse as [%v] (got [%v])", c.cpusetString, c.expected, result) } } + + negativeTestCases := []string{ + // Non-numeric entries + "nonnumeric", "non-numeric", "no,numbers", "0-a", "a-0", "0,a", "a,0", "1-2,a,3-5", + // Incomplete sequences + "0,", "0,,", ",3", ",,3", "0,,3", + // Incomplete ranges and/or negative numbers + "-1", "1-", "1,2-,3", "1,-2,3", "-1--2", "--1", "1--", + // Reversed ranges + "3-0", "0--3"} + for _, c := range negativeTestCases { + result, err := Parse(c) + if err == nil { + t.Fatalf("expected parse failure of \"%s\", but it succeeded as \"%s\"", c, result.String()) + } + } } From df4f9fd692a6817e9d43b8f2a534d7f3871466f2 Mon Sep 17 00:00:00 2001 From: Frame Date: Thu, 10 Jun 2021 18:17:04 +0800 Subject: [PATCH 23/41] Fix kubelet cpuset typo --- cpuset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpuset.go b/cpuset.go index 24330e5a..b2ce7f92 100644 --- a/cpuset.go +++ b/cpuset.go @@ -297,7 +297,7 @@ func Parse(s string) (CPUSet, error) { } // Split CPU list string: - // "0-5,34,46-48 => ["0-5", "34", "46-48"] + // "0-5,34,46-48" => ["0-5", "34", "46-48"] ranges := strings.Split(s, ",") for _, r := range ranges { From f2bcb5154521408026c2761887689c45d734ccfb Mon Sep 17 00:00:00 2001 From: tiloso Date: Thu, 1 Jul 2021 22:27:58 +0200 Subject: [PATCH 24/41] Fix staticcheck failure in pkg/kubelet/cm/cpuset --- cpuset.go | 8 ++++---- cpuset_test.go | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cpuset.go b/cpuset.go index b2ce7f92..ffae3c58 100644 --- a/cpuset.go +++ b/cpuset.go @@ -36,8 +36,8 @@ type Builder struct { } // NewBuilder returns a mutable CPUSet builder. -func NewBuilder() Builder { - return Builder{ +func NewBuilder() *Builder { + return &Builder{ result: CPUSet{ elems: map[int]struct{}{}, }, @@ -46,7 +46,7 @@ func NewBuilder() Builder { // Add adds the supplied elements to the result. Calling Add after calling // Result has no effect. -func (b Builder) Add(elems ...int) { +func (b *Builder) Add(elems ...int) { if b.done { return } @@ -57,7 +57,7 @@ func (b Builder) Add(elems ...int) { // Result returns the result CPUSet containing all elements that were // previously added to this builder. Subsequent calls to Add have no effect. -func (b Builder) Result() CPUSet { +func (b *Builder) Result() CPUSet { b.done = true return b.result } diff --git a/cpuset_test.go b/cpuset_test.go index d79748df..09f01a84 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -19,6 +19,8 @@ package cpuset import ( "reflect" "testing" + + "github.com/stretchr/testify/require" ) func TestCPUSetBuilder(t *testing.T) { @@ -36,6 +38,9 @@ func TestCPUSetBuilder(t *testing.T) { if len(elems) != result.Size() { t.Fatalf("expected cpuset %s to have the same size as %v", result, elems) } + + b.Add(6) + require.False(t, result.Contains(6), "expected calls to Add after calling Result() to have no effect") } func TestCPUSetSize(t *testing.T) { From 0e0c35269b2f50f73d7977747d7f5c5d19d7eecc Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Thu, 9 Dec 2021 21:31:26 -0500 Subject: [PATCH 25/41] Check in OWNERS modified by update-yamlfmt.sh Signed-off-by: Davanum Srinivas --- OWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OWNERS b/OWNERS index fe38ab54..201d46be 100644 --- a/OWNERS +++ b/OWNERS @@ -1,6 +1,6 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: -- derekwaynecarr -- vishh -- ConnorDoyle + - derekwaynecarr + - vishh + - ConnorDoyle From 4104a4efb3e99a1822bdb51f48dd4992211c66fa Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 10 Dec 2021 15:18:50 -0500 Subject: [PATCH 26/41] Cleanup OWNERS files (No Activity in the last year) Signed-off-by: Davanum Srinivas --- OWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OWNERS b/OWNERS index 201d46be..d4c6257f 100644 --- a/OWNERS +++ b/OWNERS @@ -2,5 +2,6 @@ approvers: - derekwaynecarr - - vishh +emeritus_approvers: - ConnorDoyle + - vishh From 6dd4eaf9b1f05f4df6d9c6f5bb1cc1507b091e29 Mon Sep 17 00:00:00 2001 From: chymy Date: Mon, 14 Mar 2022 16:53:29 +0800 Subject: [PATCH 27/41] Fix comment typo Signed-off-by: chymy --- cpuset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpuset.go b/cpuset.go index ffae3c58..0bc4a1c3 100644 --- a/cpuset.go +++ b/cpuset.go @@ -76,7 +76,7 @@ func NewCPUSet(cpus ...int) CPUSet { return b.Result() } -// NewCPUSet returns a new CPUSet containing the supplied elements, as slice of int64. +// NewCPUSetInt64 returns a new CPUSet containing the supplied elements, as slice of int64. func NewCPUSetInt64(cpus ...int64) CPUSet { b := NewBuilder() for _, c := range cpus { From fd3ebb58ffdfbd296b5555d1987792f803b2d753 Mon Sep 17 00:00:00 2001 From: "jesse.tang" <1430482733@qq.com> Date: Sat, 1 Oct 2022 07:56:25 +0800 Subject: [PATCH 28/41] Optimize: file /cpuset slice make cap (#112270) --- cpuset.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpuset.go b/cpuset.go index 0bc4a1c3..78f68c43 100644 --- a/cpuset.go +++ b/cpuset.go @@ -191,7 +191,7 @@ func (s CPUSet) Difference(s2 CPUSet) CPUSet { // ToSlice returns a slice of integers that contains all elements from // this set. func (s CPUSet) ToSlice() []int { - result := []int{} + result := make([]int, 0, len(s.elems)) for cpu := range s.elems { result = append(result, cpu) } @@ -202,7 +202,7 @@ func (s CPUSet) ToSlice() []int { // ToSliceNoSort returns a slice of integers that contains all elements from // this set. func (s CPUSet) ToSliceNoSort() []int { - result := []int{} + result := make([]int, 0, len(s.elems)) for cpu := range s.elems { result = append(result, cpu) } @@ -212,7 +212,7 @@ func (s CPUSet) ToSliceNoSort() []int { // ToSliceInt64 returns an ordered slice of int64 that contains all elements from // this set func (s CPUSet) ToSliceInt64() []int64 { - var result []int64 + result := make([]int64, 0, len(s.elems)) for cpu := range s.elems { result = append(result, int64(cpu)) } @@ -223,7 +223,7 @@ func (s CPUSet) ToSliceInt64() []int64 { // ToSliceNoSortInt64 returns a slice of int64 that contains all elements from // this set. func (s CPUSet) ToSliceNoSortInt64() []int64 { - var result []int64 + result := make([]int64, 0, len(s.elems)) for cpu := range s.elems { result = append(result, int64(cpu)) } From 145217eed5a5405bc8c3dd6abc58dc10a9c00832 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 8 Nov 2022 05:30:02 +0000 Subject: [PATCH 29/41] cpuset: Convert Union arguments to variadic This allows Union to implement UnionAll easily. --- cpuset.go | 16 +-------------- cpuset_test.go | 56 ++++++++++++++++++-------------------------------- 2 files changed, 21 insertions(+), 51 deletions(-) diff --git a/cpuset.go b/cpuset.go index 78f68c43..a3b178d5 100644 --- a/cpuset.go +++ b/cpuset.go @@ -145,23 +145,9 @@ func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { } // Union returns a new CPU set that contains all of the elements from this -// set and all of the elements from the supplied set, without mutating -// either source set. -func (s CPUSet) Union(s2 CPUSet) CPUSet { - b := NewBuilder() - for cpu := range s.elems { - b.Add(cpu) - } - for cpu := range s2.elems { - b.Add(cpu) - } - return b.Result() -} - -// UnionAll returns a new CPU set that contains all of the elements from this // set and all of the elements from the supplied sets, without mutating // either source set. -func (s CPUSet) UnionAll(s2 []CPUSet) CPUSet { +func (s CPUSet) Union(s2 ...CPUSet) CPUSet { b := NewBuilder() for cpu := range s.elems { b.Add(cpu) diff --git a/cpuset_test.go b/cpuset_test.go index 09f01a84..1e5a664b 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -175,55 +175,39 @@ func TestCPUSetIsSubsetOf(t *testing.T) { } } -func TestCPUSetUnionAll(t *testing.T) { - testCases := []struct { - s1 CPUSet - s2 CPUSet - s3 CPUSet - expected CPUSet - }{ - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(4, 5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(4), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 5), NewCPUSet(1, 2, 3, 4, 5)}, - } - for _, c := range testCases { - s := []CPUSet{} - s = append(s, c.s2) - s = append(s, c.s3) - result := c.s1.UnionAll(s) - if !result.Equals(c.expected) { - t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) - } - } -} - func TestCPUSetUnion(t *testing.T) { testCases := []struct { s1 CPUSet - s2 CPUSet + others []CPUSet expected CPUSet }{ - {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + {NewCPUSet(5), []CPUSet{}, NewCPUSet(5)}, - {NewCPUSet(), NewCPUSet(5), NewCPUSet(5)}, - {NewCPUSet(5), NewCPUSet(), NewCPUSet(5)}, - {NewCPUSet(5), NewCPUSet(5), NewCPUSet(5)}, + {NewCPUSet(), []CPUSet{NewCPUSet()}, NewCPUSet()}, - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(), []CPUSet{NewCPUSet(5)}, NewCPUSet(5)}, + {NewCPUSet(5), []CPUSet{NewCPUSet()}, NewCPUSet(5)}, + {NewCPUSet(5), []CPUSet{NewCPUSet(5)}, NewCPUSet(5)}, + + {NewCPUSet(), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet()}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + + {NewCPUSet(5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2), []CPUSet{NewCPUSet(3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3), []CPUSet{NewCPUSet(3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(), []CPUSet{NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(), NewCPUSet(4)}, NewCPUSet(1, 2, 3, 4, 5)}, + {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, } for _, c := range testCases { - result := c.s1.Union(c.s2) + result := c.s1.Union(c.others...) if !result.Equals(c.expected) { - t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), others: [%v]", c.expected, result, c.others) } } } From 1d9bc82cb35ff4679f4b60a8d57ffb5daff19af2 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 8 Nov 2022 06:13:27 +0000 Subject: [PATCH 30/41] cpuset: Remove 'MustParse' method Removes exit/fatal from cpuset library. Usage in podresources test was not necessary. Library reference in cpu_manager_test was moved to a local function, and converted to use e2e test framework error catching. --- cpuset.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/cpuset.go b/cpuset.go index a3b178d5..f120bbe3 100644 --- a/cpuset.go +++ b/cpuset.go @@ -19,13 +19,10 @@ package cpuset import ( "bytes" "fmt" - "os" "reflect" "sort" "strconv" "strings" - - "k8s.io/klog/v2" ) // Builder is a mutable builder for CPUSet. Functions that mutate instances @@ -259,18 +256,6 @@ func (s CPUSet) String() string { return strings.TrimRight(result.String(), ",") } -// MustParse CPUSet constructs a new CPU set from a Linux CPU list formatted -// string. Unlike Parse, it does not return an error but rather panics if the -// input cannot be used to construct a CPU set. -func MustParse(s string) CPUSet { - res, err := Parse(s) - if err != nil { - klog.ErrorS(err, "Failed to parse input as CPUSet", "input", s) - os.Exit(1) - } - return res -} - // Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string. // // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS From 0ac3d42a489a462207ebbc6ca948e3184289de3e Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 8 Nov 2022 05:54:42 +0000 Subject: [PATCH 31/41] cpuset: Remove *Int64 methods These are rarely used and can be accommodated with a trivial helper. --- cpuset.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/cpuset.go b/cpuset.go index f120bbe3..4abcddb6 100644 --- a/cpuset.go +++ b/cpuset.go @@ -73,15 +73,6 @@ func NewCPUSet(cpus ...int) CPUSet { return b.Result() } -// NewCPUSetInt64 returns a new CPUSet containing the supplied elements, as slice of int64. -func NewCPUSetInt64(cpus ...int64) CPUSet { - b := NewBuilder() - for _, c := range cpus { - b.Add(int(c)) - } - return b.Result() -} - // Size returns the number of elements in this set. func (s CPUSet) Size() int { return len(s.elems) @@ -192,27 +183,6 @@ func (s CPUSet) ToSliceNoSort() []int { return result } -// ToSliceInt64 returns an ordered slice of int64 that contains all elements from -// this set -func (s CPUSet) ToSliceInt64() []int64 { - result := make([]int64, 0, len(s.elems)) - for cpu := range s.elems { - result = append(result, int64(cpu)) - } - sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) - return result -} - -// ToSliceNoSortInt64 returns a slice of int64 that contains all elements from -// this set. -func (s CPUSet) ToSliceNoSortInt64() []int64 { - result := make([]int64, 0, len(s.elems)) - for cpu := range s.elems { - result = append(result, int64(cpu)) - } - return result -} - // String returns a new string representation of the elements in this CPU set // in canonical linux CPU list format. // From 8370a8e9363d62223b2ed0e6e0c738c1ea84c7bb Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 8 Nov 2022 15:09:09 +0000 Subject: [PATCH 32/41] cpuset: Make 'ToSlice*' methods look like 'set' methods In 'set', conversions to slice are done also, but with different names: ToSliceNoSort() -> UnsortedList() ToSlice() -> List() Reimplement List() in terms of UnsortedList to save some duplication. --- cpuset.go | 17 +++++++---------- cpuset_test.go | 4 ++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cpuset.go b/cpuset.go index 4abcddb6..dc047fd4 100644 --- a/cpuset.go +++ b/cpuset.go @@ -162,20 +162,17 @@ func (s CPUSet) Difference(s2 CPUSet) CPUSet { return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) }) } -// ToSlice returns a slice of integers that contains all elements from -// this set. -func (s CPUSet) ToSlice() []int { - result := make([]int, 0, len(s.elems)) - for cpu := range s.elems { - result = append(result, cpu) - } +// List returns a slice of integers that contains all elements from +// this set. The list is sorted. +func (s CPUSet) List() []int { + result := s.UnsortedList() sort.Ints(result) return result } -// ToSliceNoSort returns a slice of integers that contains all elements from +// UnsortedList returns a slice of integers that contains all elements from // this set. -func (s CPUSet) ToSliceNoSort() []int { +func (s CPUSet) UnsortedList() []int { result := make([]int, 0, len(s.elems)) for cpu := range s.elems { result = append(result, cpu) @@ -192,7 +189,7 @@ func (s CPUSet) String() string { return "" } - elems := s.ToSlice() + elems := s.List() type rng struct { start int diff --git a/cpuset_test.go b/cpuset_test.go index 1e5a664b..88186518 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -274,7 +274,7 @@ func TestCPUSetDifference(t *testing.T) { } } -func TestCPUSetToSlice(t *testing.T) { +func TestCPUSetList(t *testing.T) { testCases := []struct { set CPUSet expected []int @@ -285,7 +285,7 @@ func TestCPUSetToSlice(t *testing.T) { } for _, c := range testCases { - result := c.set.ToSlice() + result := c.set.List() if !reflect.DeepEqual(result, c.expected) { t.Fatalf("expected set as slice to be [%v] (got [%v]), s: [%v]", c.expected, result, c.set) } From d373be620a3b0ee767f9ea10faa015014dc0322b Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Mon, 19 Dec 2022 16:26:59 +0000 Subject: [PATCH 33/41] cpuset: hide 'Filter' API FilterNot is only used in this file, and is trivially converted to a 'filter' call site by inverting the predicate. Filter is only used in this file, so don't export it. --- cpuset.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/cpuset.go b/cpuset.go index dc047fd4..4c25e53b 100644 --- a/cpuset.go +++ b/cpuset.go @@ -95,9 +95,9 @@ func (s CPUSet) Equals(s2 CPUSet) bool { return reflect.DeepEqual(s.elems, s2.elems) } -// Filter returns a new CPU set that contains all of the elements from this +// filter returns a new CPU set that contains all of the elements from this // set that match the supplied predicate, without mutating the source set. -func (s CPUSet) Filter(predicate func(int) bool) CPUSet { +func (s CPUSet) filter(predicate func(int) bool) CPUSet { b := NewBuilder() for cpu := range s.elems { if predicate(cpu) { @@ -107,19 +107,6 @@ func (s CPUSet) Filter(predicate func(int) bool) CPUSet { return b.Result() } -// FilterNot returns a new CPU set that contains all of the elements from this -// set that do not match the supplied predicate, without mutating the source -// set. -func (s CPUSet) FilterNot(predicate func(int) bool) CPUSet { - b := NewBuilder() - for cpu := range s.elems { - if !predicate(cpu) { - b.Add(cpu) - } - } - return b.Result() -} - // IsSubsetOf returns true if the supplied set contains all the elements func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { result := true @@ -152,14 +139,14 @@ func (s CPUSet) Union(s2 ...CPUSet) CPUSet { // that are present in both this set and the supplied set, without mutating // either source set. func (s CPUSet) Intersection(s2 CPUSet) CPUSet { - return s.Filter(func(cpu int) bool { return s2.Contains(cpu) }) + return s.filter(func(cpu int) bool { return s2.Contains(cpu) }) } // Difference returns a new CPU set that contains all of the elements that // are present in this set and not the supplied set, without mutating either // source set. func (s CPUSet) Difference(s2 CPUSet) CPUSet { - return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) }) + return s.filter(func(cpu int) bool { return !s2.Contains(cpu) }) } // List returns a slice of integers that contains all elements from From 26783f0f021708900641b831d68fc2ecc0522a09 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Mon, 19 Dec 2022 17:29:49 +0000 Subject: [PATCH 34/41] cpuset: Rename 'NewCPUSet' to 'New' --- cpuset.go | 12 ++-- cpuset_test.go | 158 ++++++++++++++++++++++++------------------------- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/cpuset.go b/cpuset.go index 4c25e53b..ab436731 100644 --- a/cpuset.go +++ b/cpuset.go @@ -64,8 +64,8 @@ type CPUSet struct { elems map[int]struct{} } -// NewCPUSet returns a new CPUSet containing the supplied elements. -func NewCPUSet(cpus ...int) CPUSet { +// New returns a new CPUSet containing the supplied elements. +func New(cpus ...int) CPUSet { b := NewBuilder() for _, c := range cpus { b.Add(c) @@ -231,21 +231,21 @@ func Parse(s string) (CPUSet, error) { // Handle ranges that consist of only one element like "34". elem, err := strconv.Atoi(boundaries[0]) if err != nil { - return NewCPUSet(), err + return New(), err } b.Add(elem) } else if len(boundaries) == 2 { // Handle multi-element ranges like "0-5". start, err := strconv.Atoi(boundaries[0]) if err != nil { - return NewCPUSet(), err + return New(), err } end, err := strconv.Atoi(boundaries[1]) if err != nil { - return NewCPUSet(), err + return New(), err } if start > end { - return NewCPUSet(), fmt.Errorf("invalid range %q (%d >= %d)", r, start, end) + return New(), fmt.Errorf("invalid range %q (%d >= %d)", r, start, end) } // start == end is acceptable (1-1 -> 1) diff --git a/cpuset_test.go b/cpuset_test.go index 88186518..d8dcab37 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -48,9 +48,9 @@ func TestCPUSetSize(t *testing.T) { cpuset CPUSet expected int }{ - {NewCPUSet(), 0}, - {NewCPUSet(5), 1}, - {NewCPUSet(1, 2, 3, 4, 5), 5}, + {New(), 0}, + {New(5), 1}, + {New(1, 2, 3, 4, 5), 5}, } for _, c := range testCases { @@ -66,9 +66,9 @@ func TestCPUSetIsEmpty(t *testing.T) { cpuset CPUSet expected bool }{ - {NewCPUSet(), true}, - {NewCPUSet(5), false}, - {NewCPUSet(1, 2, 3, 4, 5), false}, + {New(), true}, + {New(5), false}, + {New(1, 2, 3, 4, 5), false}, } for _, c := range testCases { @@ -85,9 +85,9 @@ func TestCPUSetContains(t *testing.T) { mustContain []int mustNotContain []int }{ - {NewCPUSet(), []int{}, []int{1, 2, 3, 4, 5}}, - {NewCPUSet(5), []int{5}, []int{1, 2, 3, 4}}, - {NewCPUSet(1, 2, 4, 5), []int{1, 2, 4, 5}, []int{0, 3, 6}}, + {New(), []int{}, []int{1, 2, 3, 4, 5}}, + {New(5), []int{5}, []int{1, 2, 3, 4}}, + {New(1, 2, 4, 5), []int{1, 2, 4, 5}, []int{0, 3, 6}}, } for _, c := range testCases { @@ -109,21 +109,21 @@ func TestCPUSetEqual(t *testing.T) { s1 CPUSet s2 CPUSet }{ - {NewCPUSet(), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {New(), New()}, + {New(5), New(5)}, + {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)}, } shouldNotEqual := []struct { s1 CPUSet s2 CPUSet }{ - {NewCPUSet(), NewCPUSet(5)}, - {NewCPUSet(5), NewCPUSet()}, - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5)}, + {New(), New(5)}, + {New(5), New()}, + {New(), New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), New()}, + {New(5), New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), New(5)}, } for _, c := range shouldEqual { @@ -144,18 +144,18 @@ func TestCPUSetIsSubsetOf(t *testing.T) { s2 CPUSet }{ // A set is a subset of itself - {NewCPUSet(), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {New(), New()}, + {New(5), New(5)}, + {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)}, // Empty set is a subset of every set - {NewCPUSet(), NewCPUSet(5)}, - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, + {New(), New(5)}, + {New(), New(1, 2, 3, 4, 5)}, - {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(4, 5), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(2, 3), NewCPUSet(1, 2, 3, 4, 5)}, + {New(5), New(1, 2, 3, 4, 5)}, + {New(1, 2, 3), New(1, 2, 3, 4, 5)}, + {New(4, 5), New(1, 2, 3, 4, 5)}, + {New(2, 3), New(1, 2, 3, 4, 5)}, } shouldNotBeSubset := []struct { @@ -181,27 +181,27 @@ func TestCPUSetUnion(t *testing.T) { others []CPUSet expected CPUSet }{ - {NewCPUSet(5), []CPUSet{}, NewCPUSet(5)}, + {New(5), []CPUSet{}, New(5)}, - {NewCPUSet(), []CPUSet{NewCPUSet()}, NewCPUSet()}, + {New(), []CPUSet{New()}, New()}, - {NewCPUSet(), []CPUSet{NewCPUSet(5)}, NewCPUSet(5)}, - {NewCPUSet(5), []CPUSet{NewCPUSet()}, NewCPUSet(5)}, - {NewCPUSet(5), []CPUSet{NewCPUSet(5)}, NewCPUSet(5)}, + {New(), []CPUSet{New(5)}, New(5)}, + {New(5), []CPUSet{New()}, New(5)}, + {New(5), []CPUSet{New(5)}, New(5)}, - {NewCPUSet(), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet()}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {New(), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), []CPUSet{New()}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)}, - {NewCPUSet(5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {New(5), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), []CPUSet{New(5)}, New(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2), []CPUSet{NewCPUSet(3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3), []CPUSet{NewCPUSet(3, 4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {New(1, 2), []CPUSet{New(3, 4, 5)}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3), []CPUSet{New(3, 4, 5)}, New(1, 2, 3, 4, 5)}, - {NewCPUSet(), []CPUSet{NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(4, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(), NewCPUSet(4)}, NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), []CPUSet{NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 5)}, NewCPUSet(1, 2, 3, 4, 5)}, + {New(), []CPUSet{New(1, 2, 3, 4, 5), New(4, 5)}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), []CPUSet{New(), New(4)}, New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), []CPUSet{New(1, 2, 3, 4, 5), New(1, 5)}, New(1, 2, 3, 4, 5)}, } for _, c := range testCases { @@ -218,21 +218,21 @@ func TestCPUSetIntersection(t *testing.T) { s2 CPUSet expected CPUSet }{ - {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + {New(), New(), New()}, - {NewCPUSet(), NewCPUSet(5), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(5), NewCPUSet(5)}, + {New(), New(5), New()}, + {New(5), New(), New()}, + {New(5), New(5), New(5)}, - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet()}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5)}, + {New(), New(1, 2, 3, 4, 5), New()}, + {New(1, 2, 3, 4, 5), New(), New()}, + {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)}, - {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(5)}, + {New(5), New(1, 2, 3, 4, 5), New(5)}, + {New(1, 2, 3, 4, 5), New(5), New(5)}, - {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet()}, - {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(3)}, + {New(1, 2), New(3, 4, 5), New()}, + {New(1, 2, 3), New(3, 4, 5), New(3)}, } for _, c := range testCases { @@ -249,21 +249,21 @@ func TestCPUSetDifference(t *testing.T) { s2 CPUSet expected CPUSet }{ - {NewCPUSet(), NewCPUSet(), NewCPUSet()}, + {New(), New(), New()}, - {NewCPUSet(), NewCPUSet(5), NewCPUSet()}, - {NewCPUSet(5), NewCPUSet(), NewCPUSet(5)}, - {NewCPUSet(5), NewCPUSet(5), NewCPUSet()}, + {New(), New(5), New()}, + {New(5), New(), New(5)}, + {New(5), New(5), New()}, - {NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(), NewCPUSet(1, 2, 3, 4, 5)}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, + {New(), New(1, 2, 3, 4, 5), New()}, + {New(1, 2, 3, 4, 5), New(), New(1, 2, 3, 4, 5)}, + {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5), New()}, - {NewCPUSet(5), NewCPUSet(1, 2, 3, 4, 5), NewCPUSet()}, - {NewCPUSet(1, 2, 3, 4, 5), NewCPUSet(5), NewCPUSet(1, 2, 3, 4)}, + {New(5), New(1, 2, 3, 4, 5), New()}, + {New(1, 2, 3, 4, 5), New(5), New(1, 2, 3, 4)}, - {NewCPUSet(1, 2), NewCPUSet(3, 4, 5), NewCPUSet(1, 2)}, - {NewCPUSet(1, 2, 3), NewCPUSet(3, 4, 5), NewCPUSet(1, 2)}, + {New(1, 2), New(3, 4, 5), New(1, 2)}, + {New(1, 2, 3), New(3, 4, 5), New(1, 2)}, } for _, c := range testCases { @@ -279,9 +279,9 @@ func TestCPUSetList(t *testing.T) { set CPUSet expected []int }{ - {NewCPUSet(), []int{}}, - {NewCPUSet(5), []int{5}}, - {NewCPUSet(1, 2, 3, 4, 5), []int{1, 2, 3, 4, 5}}, + {New(), []int{}}, + {New(5), []int{5}}, + {New(1, 2, 3, 4, 5), []int{1, 2, 3, 4, 5}}, } for _, c := range testCases { @@ -297,10 +297,10 @@ func TestCPUSetString(t *testing.T) { set CPUSet expected string }{ - {NewCPUSet(), ""}, - {NewCPUSet(5), "5"}, - {NewCPUSet(1, 2, 3, 4, 5), "1-5"}, - {NewCPUSet(1, 2, 3, 5, 6, 8), "1-3,5-6,8"}, + {New(), ""}, + {New(5), "5"}, + {New(1, 2, 3, 4, 5), "1-5"}, + {New(1, 2, 3, 5, 6, 8), "1-3,5-6,8"}, } for _, c := range testCases { @@ -316,14 +316,14 @@ func TestParse(t *testing.T) { cpusetString string expected CPUSet }{ - {"", NewCPUSet()}, - {"5", NewCPUSet(5)}, - {"1,2,3,4,5", NewCPUSet(1, 2, 3, 4, 5)}, - {"1-5", NewCPUSet(1, 2, 3, 4, 5)}, - {"1-2,3-5", NewCPUSet(1, 2, 3, 4, 5)}, - {"5,4,3,2,1", NewCPUSet(1, 2, 3, 4, 5)}, // Range ordering - {"3-6,1-5", NewCPUSet(1, 2, 3, 4, 5, 6)}, // Overlapping ranges - {"3-3,5-5", NewCPUSet(3, 5)}, // Very short ranges + {"", New()}, + {"5", New(5)}, + {"1,2,3,4,5", New(1, 2, 3, 4, 5)}, + {"1-5", New(1, 2, 3, 4, 5)}, + {"1-2,3-5", New(1, 2, 3, 4, 5)}, + {"5,4,3,2,1", New(1, 2, 3, 4, 5)}, // Range ordering + {"3-6,1-5", New(1, 2, 3, 4, 5, 6)}, // Overlapping ranges + {"3-3,5-5", New(3, 5)}, // Very short ranges } for _, c := range positiveTestCases { From 4477621c16e849003accae3f87b8feddda942d16 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 8 Nov 2022 14:57:03 +0000 Subject: [PATCH 35/41] cpuset: Delete 'builder' methods All usage of builder pattern is convertible to cpuset.New() with the same or fewer lines of code. Migrate Builder.Add to a private method of CPUSet, with a comment that it is only intended for internal use to preserve immutable propoerty of the exported interface. This also removes 'require' library dependency, which avoids non-standard library usage. --- cpuset.go | 82 ++++++++++++++++++-------------------------------- cpuset_test.go | 22 -------------- 2 files changed, 29 insertions(+), 75 deletions(-) diff --git a/cpuset.go b/cpuset.go index ab436731..a0f6a783 100644 --- a/cpuset.go +++ b/cpuset.go @@ -25,40 +25,6 @@ import ( "strings" ) -// Builder is a mutable builder for CPUSet. Functions that mutate instances -// of this type are not thread-safe. -type Builder struct { - result CPUSet - done bool -} - -// NewBuilder returns a mutable CPUSet builder. -func NewBuilder() *Builder { - return &Builder{ - result: CPUSet{ - elems: map[int]struct{}{}, - }, - } -} - -// Add adds the supplied elements to the result. Calling Add after calling -// Result has no effect. -func (b *Builder) Add(elems ...int) { - if b.done { - return - } - for _, elem := range elems { - b.result.elems[elem] = struct{}{} - } -} - -// Result returns the result CPUSet containing all elements that were -// previously added to this builder. Subsequent calls to Add have no effect. -func (b *Builder) Result() CPUSet { - b.done = true - return b.result -} - // CPUSet is a thread-safe, immutable set-like data structure for CPU IDs. type CPUSet struct { elems map[int]struct{} @@ -66,11 +32,21 @@ type CPUSet struct { // New returns a new CPUSet containing the supplied elements. func New(cpus ...int) CPUSet { - b := NewBuilder() + s := CPUSet{ + elems: map[int]struct{}{}, + } for _, c := range cpus { - b.Add(c) + s.add(c) + } + return s +} + +// add adds the supplied elements to the CPUSet. +// It is intended for internal use only, since it mutates the CPUSet. +func (s CPUSet) add(elems ...int) { + for _, elem := range elems { + s.elems[elem] = struct{}{} } - return b.Result() } // Size returns the number of elements in this set. @@ -98,13 +74,13 @@ func (s CPUSet) Equals(s2 CPUSet) bool { // filter returns a new CPU set that contains all of the elements from this // set that match the supplied predicate, without mutating the source set. func (s CPUSet) filter(predicate func(int) bool) CPUSet { - b := NewBuilder() + r := New() for cpu := range s.elems { if predicate(cpu) { - b.Add(cpu) + r.add(cpu) } } - return b.Result() + return r } // IsSubsetOf returns true if the supplied set contains all the elements @@ -123,16 +99,16 @@ func (s CPUSet) IsSubsetOf(s2 CPUSet) bool { // set and all of the elements from the supplied sets, without mutating // either source set. func (s CPUSet) Union(s2 ...CPUSet) CPUSet { - b := NewBuilder() + r := New() for cpu := range s.elems { - b.Add(cpu) + r.add(cpu) } for _, cs := range s2 { for cpu := range cs.elems { - b.Add(cpu) + r.add(cpu) } } - return b.Result() + return r } // Intersection returns a new CPU set that contains all of the elements @@ -214,13 +190,13 @@ func (s CPUSet) String() string { // // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS func Parse(s string) (CPUSet, error) { - b := NewBuilder() - // Handle empty string. if s == "" { - return b.Result(), nil + return New(), nil } + result := New() + // Split CPU list string: // "0-5,34,46-48" => ["0-5", "34", "46-48"] ranges := strings.Split(s, ",") @@ -233,7 +209,7 @@ func Parse(s string) (CPUSet, error) { if err != nil { return New(), err } - b.Add(elem) + result.add(elem) } else if len(boundaries) == 2 { // Handle multi-element ranges like "0-5". start, err := strconv.Atoi(boundaries[0]) @@ -252,18 +228,18 @@ func Parse(s string) (CPUSet, error) { // Add all elements to the result. // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48]. for e := start; e <= end; e++ { - b.Add(e) + result.add(e) } } } - return b.Result(), nil + return result, nil } // Clone returns a copy of this CPU set. func (s CPUSet) Clone() CPUSet { - b := NewBuilder() + r := New() for elem := range s.elems { - b.Add(elem) + r.add(elem) } - return b.Result() + return r } diff --git a/cpuset_test.go b/cpuset_test.go index d8dcab37..ac4d3213 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -19,30 +19,8 @@ package cpuset import ( "reflect" "testing" - - "github.com/stretchr/testify/require" ) -func TestCPUSetBuilder(t *testing.T) { - b := NewBuilder() - elems := []int{1, 2, 3, 4, 5} - for _, elem := range elems { - b.Add(elem) - } - result := b.Result() - for _, elem := range elems { - if !result.Contains(elem) { - t.Fatalf("expected cpuset to contain element %d: [%v]", elem, result) - } - } - if len(elems) != result.Size() { - t.Fatalf("expected cpuset %s to have the same size as %v", result, elems) - } - - b.Add(6) - require.False(t, result.Contains(6), "expected calls to Add after calling Result() to have no effect") -} - func TestCPUSetSize(t *testing.T) { testCases := []struct { cpuset CPUSet From 0ab64485fe548d842c5d5cee56c7f29c191d6be6 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 3 Jan 2023 17:16:11 +0000 Subject: [PATCH 36/41] cpuset: Add package comment Describe use cases (node IDs, HT siblings, etc) Call out novelty (Linux CPU list parse/dump) Describe future work (relax immutable, refactor to use 'set') --- cpuset.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cpuset.go b/cpuset.go index a0f6a783..4c910d33 100644 --- a/cpuset.go +++ b/cpuset.go @@ -14,6 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package cpuset represents a collection of CPUs in a 'set' data structure. +// +// It can be used to represent core IDs, hyper thread siblings, CPU nodes, or processor IDs. +// +// The only special thing about this package is that +// methods are provided to convert back and forth from Linux 'list' syntax. +// See http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS for details. +// +// Future work can migrate this to use a 'set' library, and relax the dubious 'immutable' property. package cpuset import ( From b911e13e9e381f1bfa39fceb71e903c2e6f35175 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Fri, 27 Jan 2023 01:10:23 +0000 Subject: [PATCH 37/41] cpuset: Fix Parse() error message for n-k s.t. k end { - return New(), fmt.Errorf("invalid range %q (%d >= %d)", r, start, end) + return New(), fmt.Errorf("invalid range %q (%d > %d)", r, start, end) } // start == end is acceptable (1-1 -> 1) From 2160ea2241cd1fa4df6b322d32a854b008809839 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Fri, 27 Jan 2023 13:25:05 +0000 Subject: [PATCH 38/41] cpuset: Add a few more test cases Feedback from https://github.com/kubernetes/utils/pull/267 and related reviews. * Equality when insertion order is different * UnsortedList contents * Not-Subset cases * Clone coverage --- cpuset_test.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/cpuset_test.go b/cpuset_test.go index ac4d3213..823952af 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -18,6 +18,7 @@ package cpuset import ( "reflect" + "sort" "testing" ) @@ -90,6 +91,7 @@ func TestCPUSetEqual(t *testing.T) { {New(), New()}, {New(5), New(5)}, {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)}, + {New(5, 4, 3, 2, 1), New(1, 2, 3, 4, 5)}, } shouldNotEqual := []struct { @@ -139,7 +141,13 @@ func TestCPUSetIsSubsetOf(t *testing.T) { shouldNotBeSubset := []struct { s1 CPUSet s2 CPUSet - }{} + }{ + // A set with more elements is not a subset. + {New(5), New()}, + + // Disjoint set is not a subset. + {New(6), New(5)}, + } for _, c := range shouldBeSubset { if !c.s1.IsSubsetOf(c.s2) { @@ -255,17 +263,26 @@ func TestCPUSetDifference(t *testing.T) { func TestCPUSetList(t *testing.T) { testCases := []struct { set CPUSet - expected []int + expected []int // must be sorted }{ {New(), []int{}}, {New(5), []int{5}}, {New(1, 2, 3, 4, 5), []int{1, 2, 3, 4, 5}}, + {New(5, 4, 3, 2, 1), []int{1, 2, 3, 4, 5}}, } for _, c := range testCases { result := c.set.List() if !reflect.DeepEqual(result, c.expected) { - t.Fatalf("expected set as slice to be [%v] (got [%v]), s: [%v]", c.expected, result, c.set) + t.Fatalf("unexpected List() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) + } + + // We cannot rely on internal storage order details for a unit test. + // The best we can do is to sort the output of 'UnsortedList'. + result = c.set.UnsortedList() + sort.Ints(result) + if !reflect.DeepEqual(result, c.expected) { + t.Fatalf("unexpected UnsortedList() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) } } } @@ -330,3 +347,12 @@ func TestParse(t *testing.T) { } } } + +func TestClone(t *testing.T) { + original := New(1, 2, 3, 4, 5) + clone := original.Clone() + + if !original.Equals(clone) { + t.Errorf("expected clone [%v] to equal original [%v]", clone, original) + } +} From 005e8502325d052dbde076af5db695b2cfd14a0c Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 21 Feb 2023 05:22:07 +0000 Subject: [PATCH 39/41] cpuset: Convert Fatalf to Errrof in tests Use of Fatalf is not apppropriate in any of these cases: None of these failures are prerequisites. --- cpuset_test.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cpuset_test.go b/cpuset_test.go index 823952af..275cc9e6 100644 --- a/cpuset_test.go +++ b/cpuset_test.go @@ -35,7 +35,7 @@ func TestCPUSetSize(t *testing.T) { for _, c := range testCases { actual := c.cpuset.Size() if actual != c.expected { - t.Fatalf("expected: %d, actual: %d, cpuset: [%v]", c.expected, actual, c.cpuset) + t.Errorf("expected: %d, actual: %d, cpuset: [%v]", c.expected, actual, c.cpuset) } } } @@ -53,7 +53,7 @@ func TestCPUSetIsEmpty(t *testing.T) { for _, c := range testCases { actual := c.cpuset.IsEmpty() if actual != c.expected { - t.Fatalf("expected: %t, IsEmpty() returned: %t, cpuset: [%v]", c.expected, actual, c.cpuset) + t.Errorf("expected: %t, IsEmpty() returned: %t, cpuset: [%v]", c.expected, actual, c.cpuset) } } } @@ -72,12 +72,12 @@ func TestCPUSetContains(t *testing.T) { for _, c := range testCases { for _, elem := range c.mustContain { if !c.cpuset.Contains(elem) { - t.Fatalf("expected cpuset to contain element %d: [%v]", elem, c.cpuset) + t.Errorf("expected cpuset to contain element %d: [%v]", elem, c.cpuset) } } for _, elem := range c.mustNotContain { if c.cpuset.Contains(elem) { - t.Fatalf("expected cpuset not to contain element %d: [%v]", elem, c.cpuset) + t.Errorf("expected cpuset not to contain element %d: [%v]", elem, c.cpuset) } } } @@ -108,12 +108,12 @@ func TestCPUSetEqual(t *testing.T) { for _, c := range shouldEqual { if !c.s1.Equals(c.s2) { - t.Fatalf("expected cpusets to be equal: s1: [%v], s2: [%v]", c.s1, c.s2) + t.Errorf("expected cpusets to be equal: s1: [%v], s2: [%v]", c.s1, c.s2) } } for _, c := range shouldNotEqual { if c.s1.Equals(c.s2) { - t.Fatalf("expected cpusets to not be equal: s1: [%v], s2: [%v]", c.s1, c.s2) + t.Errorf("expected cpusets to not be equal: s1: [%v], s2: [%v]", c.s1, c.s2) } } } @@ -151,12 +151,12 @@ func TestCPUSetIsSubsetOf(t *testing.T) { for _, c := range shouldBeSubset { if !c.s1.IsSubsetOf(c.s2) { - t.Fatalf("expected s1 to be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) + t.Errorf("expected s1 to be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) } } for _, c := range shouldNotBeSubset { if c.s1.IsSubsetOf(c.s2) { - t.Fatalf("expected s1 to not be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) + t.Errorf("expected s1 to not be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2) } } } @@ -193,7 +193,7 @@ func TestCPUSetUnion(t *testing.T) { for _, c := range testCases { result := c.s1.Union(c.others...) if !result.Equals(c.expected) { - t.Fatalf("expected the union of s1 and s2 to be [%v] (got [%v]), others: [%v]", c.expected, result, c.others) + t.Errorf("expected the union of s1 and s2 to be [%v] (got [%v]), others: [%v]", c.expected, result, c.others) } } } @@ -224,7 +224,7 @@ func TestCPUSetIntersection(t *testing.T) { for _, c := range testCases { result := c.s1.Intersection(c.s2) if !result.Equals(c.expected) { - t.Fatalf("expected the intersection of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + t.Errorf("expected the intersection of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) } } } @@ -255,7 +255,7 @@ func TestCPUSetDifference(t *testing.T) { for _, c := range testCases { result := c.s1.Difference(c.s2) if !result.Equals(c.expected) { - t.Fatalf("expected the difference of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) + t.Errorf("expected the difference of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2) } } } @@ -274,7 +274,7 @@ func TestCPUSetList(t *testing.T) { for _, c := range testCases { result := c.set.List() if !reflect.DeepEqual(result, c.expected) { - t.Fatalf("unexpected List() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) + t.Errorf("unexpected List() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) } // We cannot rely on internal storage order details for a unit test. @@ -282,7 +282,7 @@ func TestCPUSetList(t *testing.T) { result = c.set.UnsortedList() sort.Ints(result) if !reflect.DeepEqual(result, c.expected) { - t.Fatalf("unexpected UnsortedList() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) + t.Errorf("unexpected UnsortedList() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set) } } } @@ -301,7 +301,7 @@ func TestCPUSetString(t *testing.T) { for _, c := range testCases { result := c.set.String() if result != c.expected { - t.Fatalf("expected set as string to be %s (got \"%s\"), s: [%v]", c.expected, result, c.set) + t.Errorf("expected set as string to be %s (got \"%s\"), s: [%v]", c.expected, result, c.set) } } } @@ -324,10 +324,10 @@ func TestParse(t *testing.T) { for _, c := range positiveTestCases { result, err := Parse(c.cpusetString) if err != nil { - t.Fatalf("expected error not to have occurred: %v", err) + t.Errorf("expected error not to have occurred: %v", err) } if !result.Equals(c.expected) { - t.Fatalf("expected string \"%s\" to parse as [%v] (got [%v])", c.cpusetString, c.expected, result) + t.Errorf("expected string \"%s\" to parse as [%v] (got [%v])", c.cpusetString, c.expected, result) } } @@ -343,7 +343,7 @@ func TestParse(t *testing.T) { for _, c := range negativeTestCases { result, err := Parse(c) if err == nil { - t.Fatalf("expected parse failure of \"%s\", but it succeeded as \"%s\"", c, result.String()) + t.Errorf("expected parse failure of \"%s\", but it succeeded as \"%s\"", c, result.String()) } } } From da7083c42f9626f3044063fb49656bcda8682566 Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Thu, 2 Mar 2023 19:07:37 +0000 Subject: [PATCH 40/41] cpuset: Move into cpuset/ directory --- OWNERS => cpuset/OWNERS | 0 cpuset.go => cpuset/cpuset.go | 0 cpuset_test.go => cpuset/cpuset_test.go | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename OWNERS => cpuset/OWNERS (100%) rename cpuset.go => cpuset/cpuset.go (100%) rename cpuset_test.go => cpuset/cpuset_test.go (100%) diff --git a/OWNERS b/cpuset/OWNERS similarity index 100% rename from OWNERS rename to cpuset/OWNERS diff --git a/cpuset.go b/cpuset/cpuset.go similarity index 100% rename from cpuset.go rename to cpuset/cpuset.go diff --git a/cpuset_test.go b/cpuset/cpuset_test.go similarity index 100% rename from cpuset_test.go rename to cpuset/cpuset_test.go From 3aa0d1f8ed511fd6a540d0c16bc210799cf4981c Mon Sep 17 00:00:00 2001 From: "Ian K. Coolidge" Date: Tue, 1 Nov 2022 17:12:45 +0000 Subject: [PATCH 41/41] cpuset: Fixup owners and add origin comment * Note that this came from kubernetes repository * Remove emeritus owners * Add klueska, dchen1107, ffromani, klueska, SergeyKanzhelev to owners --- cpuset/OWNERS | 7 ++++--- cpuset/cpuset.go | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cpuset/OWNERS b/cpuset/OWNERS index d4c6257f..0ec2b085 100644 --- a/cpuset/OWNERS +++ b/cpuset/OWNERS @@ -1,7 +1,8 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: + - dchen1107 - derekwaynecarr -emeritus_approvers: - - ConnorDoyle - - vishh + - ffromani + - klueska + - SergeyKanzhelev diff --git a/cpuset/cpuset.go b/cpuset/cpuset.go index f20a3fa0..52912d95 100644 --- a/cpuset/cpuset.go +++ b/cpuset/cpuset.go @@ -23,6 +23,8 @@ limitations under the License. // See http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS for details. // // Future work can migrate this to use a 'set' library, and relax the dubious 'immutable' property. +// +// This package was originally developed in the 'kubernetes' repository. package cpuset import (