mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
220 lines
4.9 KiB
Go
220 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
// Package idset provides a Set implementation for keeping track of various
|
|
// types of numeric IDs (e.g. CoreID, ProcessID, etc.).
|
|
package idset
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-set/v3"
|
|
)
|
|
|
|
// An ID is representative of a non-negative identifier of something like
|
|
// a CPU core ID, a NUMA node ID, etc.
|
|
//
|
|
// See the hwids package for typical use cases.
|
|
type ID interface {
|
|
~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint
|
|
}
|
|
|
|
// A Set contains some IDs.
|
|
//
|
|
// See the List Format section of
|
|
// https://www.man7.org/linux/man-pages/man7/cpuset.7.html
|
|
// for more information on the syntax and utility of these sets.
|
|
type Set[T ID] struct {
|
|
items *set.Set[T]
|
|
}
|
|
|
|
// Empty creates a fresh Set with no elements.
|
|
func Empty[T ID]() *Set[T] {
|
|
return &Set[T]{
|
|
items: set.New[T](0),
|
|
}
|
|
}
|
|
|
|
// FromFunc creates a Set from the given values by first applying
|
|
// the conversion function.
|
|
func FromFunc[T ID, A any](values []A, convert func(value A) T) *Set[T] {
|
|
return &Set[T]{
|
|
items: set.FromFunc(values, convert),
|
|
}
|
|
}
|
|
|
|
// Copy creates a deep copy of s.
|
|
func (s *Set[T]) Copy() *Set[T] {
|
|
return &Set[T]{items: s.items.Copy()}
|
|
}
|
|
|
|
var (
|
|
numberRe = regexp.MustCompile(`^\d+$`)
|
|
spanRe = regexp.MustCompile(`^(\d+)-(\d+)$`)
|
|
)
|
|
|
|
func atoi[T ID](s string) T {
|
|
i, _ := strconv.Atoi(s)
|
|
return T(i)
|
|
}
|
|
|
|
func order[T ID](a, b T) (T, T) {
|
|
if a < b {
|
|
return a, b
|
|
}
|
|
return b, a
|
|
}
|
|
|
|
// Parse the given cpuset into a set.
|
|
//
|
|
// The input is assumed to be valid.
|
|
func Parse[T ID](list string) *Set[T] {
|
|
result := Empty[T]()
|
|
|
|
add := func(s string) {
|
|
s = strings.TrimSpace(s)
|
|
switch {
|
|
case numberRe.MatchString(s):
|
|
result.items.Insert(atoi[T](s))
|
|
case spanRe.MatchString(s):
|
|
values := spanRe.FindStringSubmatch(s)
|
|
low, high := order(atoi[T](values[1]), atoi[T](values[2]))
|
|
for i := low; i <= high; i++ {
|
|
result.items.Insert(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
pieces := strings.Split(list, ",")
|
|
for _, piece := range pieces {
|
|
add(piece)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// From returns Set created from the given slice.
|
|
func From[T, U ID](slice []U) *Set[T] {
|
|
result := Empty[T]()
|
|
for _, item := range slice {
|
|
result.items.Insert(T(item))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Difference returns the set of elements in s but not in other.
|
|
func (s *Set[T]) Difference(other *Set[T]) *Set[T] {
|
|
diff := s.items.Difference(other.items)
|
|
return &Set[T]{items: diff.(*set.Set[T])}
|
|
}
|
|
|
|
// Intersect returns the set of elements that are in both s and other.
|
|
func (s *Set[T]) Intersect(other *Set[T]) *Set[T] {
|
|
intersection := s.items.Intersect(other.items)
|
|
return &Set[T]{items: intersection.(*set.Set[T])}
|
|
}
|
|
|
|
// Contains returns whether the Set contains item.
|
|
func (s *Set[T]) Contains(item T) bool {
|
|
return s.items.Contains(item)
|
|
}
|
|
|
|
// Insert item into the Set.
|
|
func (s *Set[T]) Insert(item T) {
|
|
s.items.Insert(item)
|
|
}
|
|
|
|
// Slice returns a slice copy of the Set.
|
|
func (s *Set[T]) Slice() []T {
|
|
items := s.items.Slice()
|
|
slices.Sort(items)
|
|
return items
|
|
}
|
|
|
|
// InsertSet inserts all items of other into s.
|
|
func (s *Set[T]) InsertSet(other *Set[T]) {
|
|
s.items.InsertSet(other.items)
|
|
}
|
|
|
|
// RemoveSet removes all items of other from s.
|
|
func (s *Set[T]) RemoveSet(other *Set[T]) {
|
|
s.items.RemoveSet(other.items)
|
|
}
|
|
|
|
// String creates a well-formed cpuset string representation of the Set.
|
|
func (s *Set[T]) String() string {
|
|
if s.items.Empty() {
|
|
// cgroups notation uses a space (or newline) to indicate
|
|
// "empty"; and this value is written to cgroups interface
|
|
// files
|
|
const empty = " "
|
|
return empty
|
|
}
|
|
|
|
var parts []string
|
|
ids := s.Slice()
|
|
|
|
low, high := ids[0], ids[0]
|
|
for i := 1; i < len(ids); i++ {
|
|
switch {
|
|
case ids[i] == high+1:
|
|
high = ids[i]
|
|
continue
|
|
case low == high:
|
|
parts = append(parts, fmt.Sprintf("%d", low))
|
|
default:
|
|
parts = append(parts, fmt.Sprintf("%d-%d", low, high))
|
|
}
|
|
low, high = ids[i], ids[i] // new range
|
|
}
|
|
|
|
if low == high {
|
|
parts = append(parts, fmt.Sprintf("%d", low))
|
|
} else {
|
|
parts = append(parts, fmt.Sprintf("%d-%d", low, high))
|
|
}
|
|
|
|
return strings.Join(parts, ",")
|
|
}
|
|
|
|
// ForEach iterates the elements in the set and applies f. Iteration stops
|
|
// if the result of f is a non-nil error.
|
|
func (s *Set[T]) ForEach(f func(id T) error) error {
|
|
for id := range s.items.Items() {
|
|
if err := f(id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Size returns the number of elements in the Set.
|
|
func (s *Set[T]) Size() int {
|
|
return s.items.Size()
|
|
}
|
|
|
|
// Empty returns whether the set is empty.
|
|
func (s *Set[T]) Empty() bool {
|
|
if s == nil || s.items == nil {
|
|
return true
|
|
}
|
|
return s.items.Empty()
|
|
}
|
|
|
|
// InsertSlice is used to bludgen a slice of integers into s.
|
|
func InsertSlice[T ID, X ~uint16](s *Set[T], items ...X) {
|
|
for _, item := range items {
|
|
s.Insert(T(item))
|
|
}
|
|
}
|
|
|
|
// Superset returns true of s is a superset of other.
|
|
func (s *Set[T]) Superset(other *Set[T]) bool {
|
|
// todo(shoenig) optimize
|
|
return s.items.ContainsSlice(other.items.Slice())
|
|
}
|