gvisor/runsc/specutils/cpu.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)
}