TUN-4356: Set AUTOMAXPROCS to the CPU limit when running in a Linux container

This commit is contained in:
Adam Chalmers
2021-05-06 12:22:43 -05:00
committed by Areg Harutyunyan
parent 209091da39
commit 07af2a33b7
28 changed files with 1244 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// +build linux
package cgroups
import (
"bufio"
"io"
"os"
"path/filepath"
"strconv"
)
// CGroup represents the data structure for a Linux control group.
type CGroup struct {
path string
}
// NewCGroup returns a new *CGroup from a given path.
func NewCGroup(path string) *CGroup {
return &CGroup{path: path}
}
// Path returns the path of the CGroup*.
func (cg *CGroup) Path() string {
return cg.path
}
// ParamPath returns the path of the given cgroup param under itself.
func (cg *CGroup) ParamPath(param string) string {
return filepath.Join(cg.path, param)
}
// readFirstLine reads the first line from a cgroup param file.
func (cg *CGroup) readFirstLine(param string) (string, error) {
paramFile, err := os.Open(cg.ParamPath(param))
if err != nil {
return "", err
}
defer paramFile.Close()
scanner := bufio.NewScanner(paramFile)
if scanner.Scan() {
return scanner.Text(), nil
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", io.ErrUnexpectedEOF
}
// readInt parses the first line from a cgroup param file as int.
func (cg *CGroup) readInt(param string) (int, error) {
text, err := cg.readFirstLine(param)
if err != nil {
return 0, err
}
return strconv.Atoi(text)
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// +build linux
package cgroups
const (
// _cgroupFSType is the Linux CGroup file system type used in
// `/proc/$PID/mountinfo`.
_cgroupFSType = "cgroup"
// _cgroupSubsysCPU is the CPU CGroup subsystem.
_cgroupSubsysCPU = "cpu"
// _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem.
_cgroupSubsysCPUAcct = "cpuacct"
// _cgroupSubsysCPUSet is the CPUSet CGroup subsystem.
_cgroupSubsysCPUSet = "cpuset"
// _cgroupSubsysMemory is the Memory CGroup subsystem.
_cgroupSubsysMemory = "memory"
// _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota
// parameter.
_cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us"
// _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period
// parameter.
_cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us"
)
const (
_procPathCGroup = "/proc/self/cgroup"
_procPathMountInfo = "/proc/self/mountinfo"
)
// CGroups is a map that associates each CGroup with its subsystem name.
type CGroups map[string]*CGroup
// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files
// under for some process under `/proc` file system (see also proc(5) for more
// information).
func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) {
cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup)
if err != nil {
return nil, err
}
cgroups := make(CGroups)
newMountPoint := func(mp *MountPoint) error {
if mp.FSType != _cgroupFSType {
return nil
}
for _, opt := range mp.SuperOptions {
subsys, exists := cgroupSubsystems[opt]
if !exists {
continue
}
cgroupPath, err := mp.Translate(subsys.Name)
if err != nil {
return err
}
cgroups[opt] = NewCGroup(cgroupPath)
}
return nil
}
if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil {
return nil, err
}
return cgroups, nil
}
// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current
// process.
func NewCGroupsForCurrentProcess() (CGroups, error) {
return NewCGroups(_procPathMountInfo, _procPathCGroup)
}
// CPUQuota returns the CPU quota applied with the CPU cgroup controller.
// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of
// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`.
func (cg CGroups) CPUQuota() (float64, bool, error) {
cpuCGroup, exists := cg[_cgroupSubsysCPU]
if !exists {
return -1, false, nil
}
cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
if defined := cfsQuotaUs > 0; err != nil || !defined {
return -1, defined, err
}
cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)
if err != nil {
return -1, false, err
}
return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package cgroups provides utilities to access Linux control group (CGroups)
// parameters (CPU quota, for example) for a given process.
package cgroups

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// +build linux
package cgroups
import "fmt"
type cgroupSubsysFormatInvalidError struct {
line string
}
type mountPointFormatInvalidError struct {
line string
}
type pathNotExposedFromMountPointError struct {
mountPoint string
root string
path string
}
func (err cgroupSubsysFormatInvalidError) Error() string {
return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line)
}
func (err mountPointFormatInvalidError) Error() string {
return fmt.Sprintf("invalid format for MountPoint: %q", err.line)
}
func (err pathNotExposedFromMountPointError) Error() string {
return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint)
}

View File

@@ -0,0 +1,166 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// +build linux
package cgroups
import (
"bufio"
"os"
"path/filepath"
"strconv"
"strings"
)
const (
_mountInfoSep = " "
_mountInfoOptsSep = ","
_mountInfoOptionalFieldsSep = "-"
)
const (
_miFieldIDMountID = iota
_miFieldIDParentID
_miFieldIDDeviceID
_miFieldIDRoot
_miFieldIDMountPoint
_miFieldIDOptions
_miFieldIDOptionalFields
_miFieldCountFirstHalf
)
const (
_miFieldOffsetFSType = iota
_miFieldOffsetMountSource
_miFieldOffsetSuperOptions
_miFieldCountSecondHalf
)
const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf
// MountPoint is the data structure for the mount points in
// `/proc/$PID/mountinfo`. See also proc(5) for more information.
type MountPoint struct {
MountID int
ParentID int
DeviceID string
Root string
MountPoint string
Options []string
OptionalFields []string
FSType string
MountSource string
SuperOptions []string
}
// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and
// returns a new *MountPoint.
func NewMountPointFromLine(line string) (*MountPoint, error) {
fields := strings.Split(line, _mountInfoSep)
if len(fields) < _miFieldCountMin {
return nil, mountPointFormatInvalidError{line}
}
mountID, err := strconv.Atoi(fields[_miFieldIDMountID])
if err != nil {
return nil, err
}
parentID, err := strconv.Atoi(fields[_miFieldIDParentID])
if err != nil {
return nil, err
}
for i, field := range fields[_miFieldIDOptionalFields:] {
if field == _mountInfoOptionalFieldsSep {
fsTypeStart := _miFieldIDOptionalFields + i + 1
if len(fields) != fsTypeStart+_miFieldCountSecondHalf {
return nil, mountPointFormatInvalidError{line}
}
miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart
miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart
miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart
return &MountPoint{
MountID: mountID,
ParentID: parentID,
DeviceID: fields[_miFieldIDDeviceID],
Root: fields[_miFieldIDRoot],
MountPoint: fields[_miFieldIDMountPoint],
Options: strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep),
OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)],
FSType: fields[miFieldIDFSType],
MountSource: fields[miFieldIDMountSource],
SuperOptions: strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep),
}, nil
}
}
return nil, mountPointFormatInvalidError{line}
}
// Translate converts an absolute path inside the *MountPoint's file system to
// the host file system path in the mount namespace the *MountPoint belongs to.
func (mp *MountPoint) Translate(absPath string) (string, error) {
relPath, err := filepath.Rel(mp.Root, absPath)
if err != nil {
return "", err
}
if relPath == ".." || strings.HasPrefix(relPath, "../") {
return "", pathNotExposedFromMountPointError{
mountPoint: mp.MountPoint,
root: mp.Root,
path: absPath,
}
}
return filepath.Join(mp.MountPoint, relPath), nil
}
// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`)
// and yields parsed *MountPoint into newMountPoint.
func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error {
mountInfoFile, err := os.Open(procPathMountInfo)
if err != nil {
return err
}
defer mountInfoFile.Close()
scanner := bufio.NewScanner(mountInfoFile)
for scanner.Scan() {
mountPoint, err := NewMountPointFromLine(scanner.Text())
if err != nil {
return err
}
if err := newMountPoint(mountPoint); err != nil {
return err
}
}
return scanner.Err()
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// +build linux
package cgroups
import (
"bufio"
"os"
"strconv"
"strings"
)
const (
_cgroupSep = ":"
_cgroupSubsysSep = ","
)
const (
_csFieldIDID = iota
_csFieldIDSubsystems
_csFieldIDName
_csFieldCount
)
// CGroupSubsys represents the data structure for entities in
// `/proc/$PID/cgroup`. See also proc(5) for more information.
type CGroupSubsys struct {
ID int
Subsystems []string
Name string
}
// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in
// the format of `/proc/$PID/cgroup`
func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) {
fields := strings.SplitN(line, _cgroupSep, _csFieldCount)
if len(fields) != _csFieldCount {
return nil, cgroupSubsysFormatInvalidError{line}
}
id, err := strconv.Atoi(fields[_csFieldIDID])
if err != nil {
return nil, err
}
cgroup := &CGroupSubsys{
ID: id,
Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep),
Name: fields[_csFieldIDName],
}
return cgroup, nil
}
// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`)
// and returns a new map[string]*CGroupSubsys.
func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) {
cgroupFile, err := os.Open(procPathCGroup)
if err != nil {
return nil, err
}
defer cgroupFile.Close()
scanner := bufio.NewScanner(cgroupFile)
subsystems := make(map[string]*CGroupSubsys)
for scanner.Scan() {
cgroup, err := NewCGroupSubsysFromLine(scanner.Text())
if err != nil {
return nil, err
}
for _, subsys := range cgroup.Subsystems {
subsystems[subsys] = cgroup
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return subsystems, nil
}