-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for setting GOMAXPROCS based on CPU allotment in a system with cgroups v2. This works by adding a new internal CGroups2 type that is able to provide the CPU quota if the system is using cgroups v2. In the main GOMAXPROCS assignment logic, we use this variant if we're able to, falling back to the v1 version if not. Resolves #21 Co-authored-by: Abhinav Gupta <[email protected]> Co-authored-by: Matt Way <[email protected]>
- Loading branch information
1 parent
7ff767a
commit ad6f05b
Showing
17 changed files
with
461 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
// Copyright (c) 2022 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. | ||
|
||
//go:build linux | ||
// +build linux | ||
|
||
package cgroups | ||
|
||
import ( | ||
"bufio" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
const ( | ||
// _cgroupv2CPUMax is the file name for the CGroup-V2 CPU max and period | ||
// parameter. | ||
_cgroupv2CPUMax = "cpu.max" | ||
// _cgroupFSType is the Linux CGroup-V2 file system type used in | ||
// `/proc/$PID/mountinfo`. | ||
_cgroupv2FSType = "cgroup2" | ||
|
||
_cgroupv2MountPoint = "/sys/fs/cgroup" | ||
|
||
_cgroupV2CPUMaxDefaultPeriod = 100000 | ||
_cgroupV2CPUMaxQuotaMax = "max" | ||
) | ||
|
||
const ( | ||
_cgroupv2CPUMaxQuotaIndex = iota | ||
_cgroupv2CPUMaxPeriodIndex | ||
) | ||
|
||
// ErrNotV2 indicates that the system is not using cgroups2. | ||
var ErrNotV2 = errors.New("not using cgroups2") | ||
|
||
// CGroups2 provides access to cgroups data for systems using cgroups2. | ||
type CGroups2 struct { | ||
mountPoint string | ||
cpuMaxFile string | ||
} | ||
|
||
// NewCGroups2ForCurrentProcess builds a CGroups2 for the current process. | ||
// | ||
// This returns ErrNotV2 if the system is not using cgroups2. | ||
func NewCGroups2ForCurrentProcess() (*CGroups2, error) { | ||
return newCGroups2FromMountInfo(_procPathMountInfo) | ||
} | ||
|
||
func newCGroups2FromMountInfo(mountInfoPath string) (*CGroups2, error) { | ||
isV2, err := isCGroupV2(mountInfoPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !isV2 { | ||
return nil, ErrNotV2 | ||
} | ||
|
||
return &CGroups2{ | ||
mountPoint: _cgroupv2MountPoint, | ||
cpuMaxFile: _cgroupv2CPUMax, | ||
}, nil | ||
} | ||
|
||
func isCGroupV2(procPathMountInfo string) (bool, error) { | ||
var ( | ||
isV2 bool | ||
newMountPoint = func(mp *MountPoint) error { | ||
isV2 = mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint | ||
return nil | ||
} | ||
) | ||
|
||
if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { | ||
return false, err | ||
} | ||
|
||
return isV2, nil | ||
} | ||
|
||
// CPUQuota returns the CPU quota applied with the CPU cgroup2 controller. | ||
// It is a result of reading cpu quota and period from cpu.max file. | ||
// It will return `cpu.max / cpu.period`. If cpu.max is set to max, it returns | ||
// (-1, false, nil) | ||
func (cg *CGroups2) CPUQuota() (float64, bool, error) { | ||
cpuMaxParams, err := os.Open(path.Join(cg.mountPoint, cg.cpuMaxFile)) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
return -1, false, nil | ||
} | ||
return -1, false, err | ||
} | ||
|
||
scanner := bufio.NewScanner(cpuMaxParams) | ||
if scanner.Scan() { | ||
fields := strings.Fields(scanner.Text()) | ||
if len(fields) == 0 || len(fields) > 2 { | ||
return -1, false, fmt.Errorf("invalid format") | ||
} | ||
|
||
if fields[_cgroupv2CPUMaxQuotaIndex] == _cgroupV2CPUMaxQuotaMax { | ||
return -1, false, nil | ||
} | ||
|
||
max, err := strconv.Atoi(fields[_cgroupv2CPUMaxQuotaIndex]) | ||
if err != nil { | ||
return -1, false, err | ||
} | ||
|
||
var period int | ||
if len(fields) == 1 { | ||
period = _cgroupV2CPUMaxDefaultPeriod | ||
} else { | ||
period, err = strconv.Atoi(fields[_cgroupv2CPUMaxPeriodIndex]) | ||
if err != nil { | ||
return -1, false, err | ||
} | ||
} | ||
|
||
return float64(max) / float64(period), true, nil | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
return -1, false, err | ||
} | ||
|
||
return 0, false, io.ErrUnexpectedEOF | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright (c) 2022 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. | ||
|
||
//go:build linux | ||
// +build linux | ||
|
||
package cgroups | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCGroupsIsCGroupV2(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
isV2 bool | ||
wantErr bool // should be false if isV2 is true | ||
}{ | ||
{ | ||
name: "mountinfo", | ||
isV2: false, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "mountinfo-v1-v2", | ||
isV2: false, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "mountinfo-v2", | ||
isV2: true, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "mountinfo-nonexistent", | ||
isV2: false, | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
mountInfoPath := filepath.Join(testDataProcPath, "v2", tt.name) | ||
_, err := newCGroups2FromMountInfo(mountInfoPath) | ||
switch { | ||
case tt.wantErr: | ||
assert.Error(t, err) | ||
case !tt.isV2: | ||
assert.ErrorIs(t, err, ErrNotV2) | ||
default: | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCGroupsCPUQuotaV2(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
want float64 | ||
wantOK bool | ||
wantErr string | ||
}{ | ||
{ | ||
name: "set", | ||
want: 2.5, | ||
wantOK: true, | ||
}, | ||
{ | ||
name: "unset", | ||
want: -1.0, | ||
wantOK: false, | ||
}, | ||
{ | ||
name: "only-max", | ||
want: 5.0, | ||
wantOK: true, | ||
}, | ||
{ | ||
name: "invalid-max", | ||
wantErr: `parsing "asdf": invalid syntax`, | ||
}, | ||
{ | ||
name: "invalid-period", | ||
wantErr: `parsing "njn": invalid syntax`, | ||
}, | ||
{ | ||
name: "nonexistent", | ||
want: -1.0, | ||
wantOK: false, | ||
}, | ||
{ | ||
name: "empty", | ||
wantErr: "unexpected EOF", | ||
}, | ||
{ | ||
name: "too-few-fields", | ||
wantErr: "invalid format", | ||
}, | ||
{ | ||
name: "too-many-fields", | ||
wantErr: "invalid format", | ||
}, | ||
} | ||
|
||
mountPoint := filepath.Join(testDataCGroupsPath, "v2") | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
quota, defined, err := (&CGroups2{ | ||
mountPoint: mountPoint, | ||
cpuMaxFile: tt.name, | ||
}).CPUQuota() | ||
|
||
if len(tt.wantErr) > 0 { | ||
require.Error(t, err, tt.name) | ||
assert.Contains(t, err.Error(), tt.wantErr) | ||
} else { | ||
require.NoError(t, err, tt.name) | ||
assert.Equal(t, tt.want, quota, tt.name) | ||
assert.Equal(t, tt.wantOK, defined, tt.name) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestCGroupsCPUQuotaV2_OtherErrors(t *testing.T) { | ||
t.Run("no permissions to open", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
const name = "foo" | ||
|
||
mountPoint := t.TempDir() | ||
require.NoError(t, os.WriteFile(filepath.Join(mountPoint, name), nil /* write only*/, 0222)) | ||
|
||
_, _, err := (&CGroups2{mountPoint: mountPoint, cpuMaxFile: name}).CPUQuota() | ||
require.Error(t, err) | ||
assert.Contains(t, err.Error(), "permission denied") | ||
}) | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
asdf 100000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
500000 njn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
500000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
250000 100000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
250000 100000 100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
max 100000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered | ||
2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 | ||
3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw | ||
4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw | ||
5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 | ||
6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset | ||
7 5 0:6 /docker /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct | ||
8 5 0:7 /docker /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
33 24 0:28 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755,inode64 | ||
34 33 0:29 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup2 rw,nsdelegate | ||
35 33 0:30 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,xattr,name=systemd | ||
39 33 0:34 / /sys/fs/cgroup/misc rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,misc | ||
40 33 0:35 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,net_cls,net_prio | ||
41 33 0:36 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,rdma | ||
42 33 0:37 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,memory | ||
43 33 0:38 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,blkio | ||
44 33 0:39 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,cpu,cpuacct | ||
45 33 0:40 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,pids | ||
46 33 0:41 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,hugetlb | ||
47 33 0:42 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,freezer | ||
48 33 0:43 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,perf_event | ||
49 33 0:44 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices | ||
50 33 0:45 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,cpuset |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
34 33 0:29 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw,nsdelegate |
Oops, something went wrong.