91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
// Copyright 2018 Google Inc.
|
|
//
|
|
// 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 specutils
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// CalculateCPUNumber calculates the number of CPUs that should be exposed
|
|
// inside the sandbox.
|
|
func CalculateCPUNumber(spec *specs.Spec) (int, error) {
|
|
// If spec does not contain CPU field, then return the number of host CPUs.
|
|
if spec == nil || spec.Linux == nil || spec.Linux.Resources == nil || spec.Linux.Resources.CPU == nil {
|
|
return runtime.NumCPU(), nil
|
|
}
|
|
cpuSpec := spec.Linux.Resources.CPU
|
|
|
|
// If cpuSpec.Cpus is specified, then parse and return that. They must be in
|
|
// the list format for cpusets, which is "a comma-separated list of CPU
|
|
// numbers and ranges of numbers, in ASCII decimal." --man 7 cpuset.
|
|
cpus := cpuSpec.Cpus
|
|
if cpus != "" {
|
|
cpuNum := 0
|
|
for _, subs := range strings.Split(cpus, ",") {
|
|
result, err := parseCPUNumber(subs)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
cpuNum += result
|
|
}
|
|
return cpuNum, nil
|
|
}
|
|
|
|
// If CPU.Quota and CPU.Period are specified, we can divide them to get an
|
|
// approximation of the number of CPUs needed.
|
|
if cpuSpec.Quota != nil && cpuSpec.Period != nil && *cpuSpec.Period != 0 {
|
|
cpuQuota := *cpuSpec.Quota
|
|
cpuPeriod := *cpuSpec.Period
|
|
return int(cpuQuota)/int(cpuPeriod) + 1, nil
|
|
}
|
|
|
|
// Default to number of host cpus.
|
|
return runtime.NumCPU(), nil
|
|
}
|
|
|
|
// parseCPUNumber converts a cpuset string into the number of cpus included in
|
|
// the string , e.g. "3-6" -> 4.
|
|
func parseCPUNumber(cpus string) (int, error) {
|
|
switch cpusSlice := strings.Split(cpus, "-"); len(cpusSlice) {
|
|
case 1:
|
|
// cpus is not a range. We must only check that it is a valid number.
|
|
if _, err := strconv.Atoi(cpus); err != nil {
|
|
return 0, fmt.Errorf("invalid individual cpu number %q", cpus)
|
|
}
|
|
return 1, nil
|
|
case 2:
|
|
// cpus is a range. We must check that start and end are valid numbers,
|
|
// and calculate their difference (inclusively).
|
|
first, err := strconv.Atoi(cpusSlice[0])
|
|
if err != nil || first < 0 {
|
|
return 0, fmt.Errorf("invalid first cpu number %q in range %q", cpusSlice[0], cpus)
|
|
}
|
|
last, err := strconv.Atoi(cpusSlice[1])
|
|
if err != nil || last < 0 {
|
|
return 0, fmt.Errorf("invalid last cpu number %q in range %q", cpusSlice[1], cpus)
|
|
}
|
|
cpuNum := last - first + 1
|
|
if cpuNum <= 0 {
|
|
return 0, fmt.Errorf("cpu range %q does not include positive number of cpus", cpus)
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("invalid cpu string %q", cpus)
|
|
}
|