Skip to content

Commit

Permalink
Cgroups2: Reduce allocations for manager.Stat
Browse files Browse the repository at this point in the history
This change works towards bringing down allocations for manager.Stat().
There's two things this change does:

1. Swap to opening a file and issuing a single read for uint64/"max" values.
Previously we were doing os.ReadFile's which returns a byte slice, so it needs
to allocate.
2. Sizing the map we store {controller}.stat values in. We store 40+ stats in this
map and the default bucket size for Go seems to be smaller than this, so we'd incur
a couple allocs at runtime when adding these.

Signed-off-by: Danny Canter <danny@dcantah.dev>
  • Loading branch information
dcantah committed Apr 18, 2023
1 parent fb1932a commit c84d3b1
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 8 deletions.
Binary file added cgroup2/cgroup2.test
Binary file not shown.
7 changes: 5 additions & 2 deletions cgroup2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,10 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
if err != nil {
return nil, err
}
out := make(map[string]uint64)
// Sizing this avoids an allocation to increase the map at runtime;
// currently the default bucket size is 8 and we put 40+ elements
// in it so we'd always end up allocating.
out := make(map[string]uint64, 50)
for _, controller := range controllers {
switch controller {
case "cpu", "memory":
Expand All @@ -541,8 +544,8 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
return nil, err
}
}
var metrics stats.Metrics

var metrics stats.Metrics
metrics.Pids = &stats.PidsStat{
Current: getStatFileContentUint64(filepath.Join(c.path, "pids.current")),
Limit: getStatFileContentUint64(filepath.Join(c.path, "pids.max")),
Expand Down
19 changes: 19 additions & 0 deletions cgroup2/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,22 @@ func TestCgroupType(t *testing.T) {
require.NoError(t, err)
require.Equal(t, cgType, Threaded)
}

func BenchmarkStat(b *testing.B) {
checkCgroupMode(b)
group := "/stat-test-cg"
groupPath := fmt.Sprintf("%s-%d", group, os.Getpid())
c, err := NewManager(defaultCgroup2Path, groupPath, &Resources{})
if err != nil {
b.Fatal("failed to init new cgroup manager: ", err)
}
defer os.Remove(c.path)

b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := c.Stat()
if err != nil {
b.Fatal(err)
}
}
}
6 changes: 3 additions & 3 deletions cgroup2/testutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import (
"golang.org/x/sys/unix"
)

func checkCgroupMode(t *testing.T) {
func checkCgroupMode(tb testing.TB) {
var st unix.Statfs_t
if err := unix.Statfs(defaultCgroup2Path, &st); err != nil {
t.Fatal("cannot statfs cgroup root")
tb.Fatal("cannot statfs cgroup root")
}
isUnified := st.Type == unix.CGROUP2_SUPER_MAGIC
if !isUnified {
t.Skip("System running in hybrid or cgroupv1 mode")
tb.Skip("System running in hybrid or cgroupv1 mode")
}
}

Expand Down
16 changes: 13 additions & 3 deletions cgroup2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,28 @@ func ToResources(spec *specs.LinuxResources) *Resources {

// Gets uint64 parsed content of single value cgroup stat file
func getStatFileContentUint64(filePath string) uint64 {
contents, err := os.ReadFile(filePath)
f, err := os.Open(filePath)
if err != nil {
return 0
}
trimmed := strings.TrimSpace(string(contents))
defer f.Close()

// We expect an unsigned 64 bit integer, or a "max" string
// in some cases.
buf := make([]byte, 32)
n, err := f.Read(buf)
if err != nil {
return 0
}

trimmed := strings.TrimSpace(string(buf[:n]))
if trimmed == "max" {
return math.MaxUint64
}

res, err := parseUint(trimmed, 10, 64)
if err != nil {
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), filePath)
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", trimmed, filePath)
return res
}

Expand Down
8 changes: 8 additions & 0 deletions cgroup2/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,11 @@ func TestToResources(t *testing.T) {
v2resources2 := ToResources(&res2)
assert.Equal(t, CPUMax("max 10000"), v2resources2.CPU.Max)
}

func BenchmarkGetStatFileContentUint64(b *testing.B) {
b.ReportAllocs()

for i := 0; i < b.N; i++ {
_ = getStatFileContentUint64("/proc/self/loginuid")
}
}

0 comments on commit c84d3b1

Please sign in to comment.