cpuset.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /*
  15. Copyright 2017 The Kubernetes Authors.
  16. Licensed under the Apache License, Version 2.0 (the "License");
  17. you may not use this file except in compliance with the License.
  18. You may obtain a copy of the License at
  19. http://www.apache.org/licenses/LICENSE-2.0
  20. Unless required by applicable law or agreed to in writing, software
  21. distributed under the License is distributed on an "AS IS" BASIS,
  22. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. See the License for the specific language governing permissions and
  24. limitations under the License.
  25. */
  26. package cpuset
  27. import (
  28. "bytes"
  29. "fmt"
  30. "reflect"
  31. "sort"
  32. "strconv"
  33. "strings"
  34. )
  35. // Builder is a mutable builder for CPUSet. Functions that mutate instances
  36. // of this type are not thread-safe.
  37. type Builder struct {
  38. result CPUSet
  39. done bool
  40. }
  41. // NewBuilder returns a mutable CPUSet builder.
  42. func NewBuilder() *Builder {
  43. return &Builder{
  44. result: CPUSet{
  45. elems: map[int]struct{}{},
  46. },
  47. }
  48. }
  49. // Add adds the supplied elements to the result. Calling Add after calling
  50. // Result has no effect.
  51. func (b *Builder) Add(elems ...int) {
  52. if b.done {
  53. return
  54. }
  55. for _, elem := range elems {
  56. b.result.elems[elem] = struct{}{}
  57. }
  58. }
  59. // Result returns the result CPUSet containing all elements that were
  60. // previously added to this builder. Subsequent calls to Add have no effect.
  61. func (b *Builder) Result() CPUSet {
  62. b.done = true
  63. return b.result
  64. }
  65. // CPUSet is a thread-safe, immutable set-like data structure for CPU IDs.
  66. type CPUSet struct {
  67. elems map[int]struct{}
  68. }
  69. // NewCPUSet returns a new CPUSet containing the supplied elements.
  70. func NewCPUSet(cpus ...int) CPUSet {
  71. b := NewBuilder()
  72. for _, c := range cpus {
  73. b.Add(c)
  74. }
  75. return b.Result()
  76. }
  77. // NewCPUSetInt64 returns a new CPUSet containing the supplied elements, as slice of int64.
  78. func NewCPUSetInt64(cpus ...int64) CPUSet {
  79. b := NewBuilder()
  80. for _, c := range cpus {
  81. b.Add(int(c))
  82. }
  83. return b.Result()
  84. }
  85. // Size returns the number of elements in this set.
  86. func (s CPUSet) Size() int {
  87. return len(s.elems)
  88. }
  89. // IsEmpty returns true if there are zero elements in this set.
  90. func (s CPUSet) IsEmpty() bool {
  91. return s.Size() == 0
  92. }
  93. // Contains returns true if the supplied element is present in this set.
  94. func (s CPUSet) Contains(cpu int) bool {
  95. _, found := s.elems[cpu]
  96. return found
  97. }
  98. // Equals returns true if the supplied set contains exactly the same elements
  99. // as this set (s IsSubsetOf s2 and s2 IsSubsetOf s).
  100. func (s CPUSet) Equals(s2 CPUSet) bool {
  101. return reflect.DeepEqual(s.elems, s2.elems)
  102. }
  103. // Filter returns a new CPU set that contains all of the elements from this
  104. // set that match the supplied predicate, without mutating the source set.
  105. func (s CPUSet) Filter(predicate func(int) bool) CPUSet {
  106. b := NewBuilder()
  107. for cpu := range s.elems {
  108. if predicate(cpu) {
  109. b.Add(cpu)
  110. }
  111. }
  112. return b.Result()
  113. }
  114. // FilterNot returns a new CPU set that contains all of the elements from this
  115. // set that do not match the supplied predicate, without mutating the source
  116. // set.
  117. func (s CPUSet) FilterNot(predicate func(int) bool) CPUSet {
  118. b := NewBuilder()
  119. for cpu := range s.elems {
  120. if !predicate(cpu) {
  121. b.Add(cpu)
  122. }
  123. }
  124. return b.Result()
  125. }
  126. // IsSubsetOf returns true if the supplied set contains all the elements
  127. func (s CPUSet) IsSubsetOf(s2 CPUSet) bool {
  128. result := true
  129. for cpu := range s.elems {
  130. if !s2.Contains(cpu) {
  131. result = false
  132. break
  133. }
  134. }
  135. return result
  136. }
  137. // Union returns a new CPU set that contains all of the elements from this
  138. // set and all of the elements from the supplied set, without mutating
  139. // either source set.
  140. func (s CPUSet) Union(s2 CPUSet) CPUSet {
  141. b := NewBuilder()
  142. for cpu := range s.elems {
  143. b.Add(cpu)
  144. }
  145. for cpu := range s2.elems {
  146. b.Add(cpu)
  147. }
  148. return b.Result()
  149. }
  150. // UnionAll returns a new CPU set that contains all of the elements from this
  151. // set and all of the elements from the supplied sets, without mutating
  152. // either source set.
  153. func (s CPUSet) UnionAll(s2 []CPUSet) CPUSet {
  154. b := NewBuilder()
  155. for cpu := range s.elems {
  156. b.Add(cpu)
  157. }
  158. for _, cs := range s2 {
  159. for cpu := range cs.elems {
  160. b.Add(cpu)
  161. }
  162. }
  163. return b.Result()
  164. }
  165. // Intersection returns a new CPU set that contains all of the elements
  166. // that are present in both this set and the supplied set, without mutating
  167. // either source set.
  168. func (s CPUSet) Intersection(s2 CPUSet) CPUSet {
  169. return s.Filter(func(cpu int) bool { return s2.Contains(cpu) })
  170. }
  171. // Difference returns a new CPU set that contains all of the elements that
  172. // are present in this set and not the supplied set, without mutating either
  173. // source set.
  174. func (s CPUSet) Difference(s2 CPUSet) CPUSet {
  175. return s.FilterNot(func(cpu int) bool { return s2.Contains(cpu) })
  176. }
  177. // ToSlice returns a slice of integers that contains all elements from
  178. // this set.
  179. func (s CPUSet) ToSlice() []int {
  180. result := []int{}
  181. for cpu := range s.elems {
  182. result = append(result, cpu)
  183. }
  184. sort.Ints(result)
  185. return result
  186. }
  187. // ToSliceNoSort returns a slice of integers that contains all elements from
  188. // this set.
  189. func (s CPUSet) ToSliceNoSort() []int {
  190. result := []int{}
  191. for cpu := range s.elems {
  192. result = append(result, cpu)
  193. }
  194. return result
  195. }
  196. // ToSliceInt64 returns an ordered slice of int64 that contains all elements from
  197. // this set
  198. func (s CPUSet) ToSliceInt64() []int64 {
  199. var result []int64
  200. for cpu := range s.elems {
  201. result = append(result, int64(cpu))
  202. }
  203. sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
  204. return result
  205. }
  206. // ToSliceNoSortInt64 returns a slice of int64 that contains all elements from
  207. // this set.
  208. func (s CPUSet) ToSliceNoSortInt64() []int64 {
  209. var result []int64
  210. for cpu := range s.elems {
  211. result = append(result, int64(cpu))
  212. }
  213. return result
  214. }
  215. // String returns a new string representation of the elements in this CPU set
  216. // in canonical linux CPU list format.
  217. //
  218. // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
  219. func (s CPUSet) String() string {
  220. if s.IsEmpty() {
  221. return ""
  222. }
  223. elems := s.ToSlice()
  224. type rng struct {
  225. start int
  226. end int
  227. }
  228. ranges := []rng{{elems[0], elems[0]}}
  229. for i := 1; i < len(elems); i++ {
  230. lastRange := &ranges[len(ranges)-1]
  231. // if this element is adjacent to the high end of the last range
  232. if elems[i] == lastRange.end+1 {
  233. // then extend the last range to include this element
  234. lastRange.end = elems[i]
  235. continue
  236. }
  237. // otherwise, start a new range beginning with this element
  238. ranges = append(ranges, rng{elems[i], elems[i]})
  239. }
  240. // construct string from ranges
  241. var result bytes.Buffer
  242. for _, r := range ranges {
  243. if r.start == r.end {
  244. result.WriteString(strconv.Itoa(r.start))
  245. } else {
  246. result.WriteString(fmt.Sprintf("%d-%d", r.start, r.end))
  247. }
  248. result.WriteString(",")
  249. }
  250. return strings.TrimRight(result.String(), ",")
  251. }
  252. // Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string.
  253. //
  254. // See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
  255. func Parse(s string) (CPUSet, error) {
  256. b := NewBuilder()
  257. // Handle empty string.
  258. if s == "" {
  259. return b.Result(), nil
  260. }
  261. // Split CPU list string:
  262. // "0-5,34,46-48" => ["0-5", "34", "46-48"]
  263. ranges := strings.Split(s, ",")
  264. for _, r := range ranges {
  265. boundaries := strings.SplitN(r, "-", 2)
  266. if len(boundaries) == 1 {
  267. // Handle ranges that consist of only one element like "34".
  268. elem, err := strconv.Atoi(boundaries[0])
  269. if err != nil {
  270. return NewCPUSet(), err
  271. }
  272. b.Add(elem)
  273. } else if len(boundaries) == 2 {
  274. // Handle multi-element ranges like "0-5".
  275. start, err := strconv.Atoi(boundaries[0])
  276. if err != nil {
  277. return NewCPUSet(), err
  278. }
  279. end, err := strconv.Atoi(boundaries[1])
  280. if err != nil {
  281. return NewCPUSet(), err
  282. }
  283. if start > end {
  284. return NewCPUSet(), fmt.Errorf("invalid range %q (%d >= %d)", r, start, end)
  285. }
  286. // start == end is acceptable (1-1 -> 1)
  287. // Add all elements to the result.
  288. // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48].
  289. for e := start; e <= end; e++ {
  290. b.Add(e)
  291. }
  292. }
  293. }
  294. return b.Result(), nil
  295. }
  296. // Clone returns a copy of this CPU set.
  297. func (s CPUSet) Clone() CPUSet {
  298. b := NewBuilder()
  299. for elem := range s.elems {
  300. b.Add(elem)
  301. }
  302. return b.Result()
  303. }