Skip to content

Commit 92a7d65

Browse files
committed
Cgroups2: Reduce allocations for manager.Stat
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>
1 parent 0218f40 commit 92a7d65

5 files changed

Lines changed: 46 additions & 8 deletions

File tree

cgroup2/manager.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,10 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
523523
if err != nil {
524524
return nil, err
525525
}
526-
out := make(map[string]uint64)
526+
// Sizing this avoids an allocation to increase the map at runtime;
527+
// currently the default bucket size is 8 and we put 40+ elements
528+
// in it so we'd always end up allocating.
529+
out := make(map[string]uint64, 50)
527530
for _, controller := range controllers {
528531
switch controller {
529532
case "cpu", "memory":
@@ -541,8 +544,8 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
541544
return nil, err
542545
}
543546
}
544-
var metrics stats.Metrics
545547

548+
var metrics stats.Metrics
546549
metrics.Pids = &stats.PidsStat{
547550
Current: getStatFileContentUint64(filepath.Join(c.path, "pids.current")),
548551
Limit: getStatFileContentUint64(filepath.Join(c.path, "pids.max")),

cgroup2/manager_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,20 @@ func TestCgroupType(t *testing.T) {
223223
require.NoError(t, err)
224224
require.Equal(t, cgType, Threaded)
225225
}
226+
227+
func BenchmarkStat(b *testing.B) {
228+
checkCgroupMode(b)
229+
group := "/stat-test-cg"
230+
groupPath := fmt.Sprintf("%s-%d", group, os.Getpid())
231+
c, err := NewManager(defaultCgroup2Path, groupPath, &Resources{})
232+
require.NoErrorf(b, err, "failed to init new cgroup manager")
233+
b.Cleanup(func() {
234+
_ = c.Delete()
235+
})
236+
237+
b.ReportAllocs()
238+
for i := 0; i < b.N; i++ {
239+
_, err := c.Stat()
240+
require.NoError(b, err)
241+
}
242+
}

cgroup2/testutils_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ import (
2727
"golang.org/x/sys/unix"
2828
)
2929

30-
func checkCgroupMode(t *testing.T) {
30+
func checkCgroupMode(tb testing.TB) {
3131
var st unix.Statfs_t
3232
err := unix.Statfs(defaultCgroup2Path, &st)
33-
require.NoError(t, err, "cannot statfs cgroup root")
33+
require.NoError(tb, err, "cannot statfs cgroup root")
3434

3535
isUnified := st.Type == unix.CGROUP2_SUPER_MAGIC
3636
if !isUnified {
37-
t.Skip("System running in hybrid or cgroupv1 mode")
37+
tb.Skip("System running in hybrid or cgroupv1 mode")
3838
}
3939
}
4040

cgroup2/utils.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,28 @@ func ToResources(spec *specs.LinuxResources) *Resources {
236236

237237
// Gets uint64 parsed content of single value cgroup stat file
238238
func getStatFileContentUint64(filePath string) uint64 {
239-
contents, err := os.ReadFile(filePath)
239+
f, err := os.Open(filePath)
240240
if err != nil {
241241
return 0
242242
}
243-
trimmed := strings.TrimSpace(string(contents))
243+
defer f.Close()
244+
245+
// We expect an unsigned 64 bit integer, or a "max" string
246+
// in some cases.
247+
buf := make([]byte, 32)
248+
n, err := f.Read(buf)
249+
if err != nil {
250+
return 0
251+
}
252+
253+
trimmed := strings.TrimSpace(string(buf[:n]))
244254
if trimmed == "max" {
245255
return math.MaxUint64
246256
}
247257

248258
res, err := parseUint(trimmed, 10, 64)
249259
if err != nil {
250-
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), filePath)
260+
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", trimmed, filePath)
251261
return res
252262
}
253263

cgroup2/utils_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,11 @@ func TestToResources(t *testing.T) {
5858
v2resources2 := ToResources(&res2)
5959
assert.Equal(t, CPUMax("max 10000"), v2resources2.CPU.Max)
6060
}
61+
62+
func BenchmarkGetStatFileContentUint64(b *testing.B) {
63+
b.ReportAllocs()
64+
65+
for i := 0; i < b.N; i++ {
66+
_ = getStatFileContentUint64("/proc/self/loginuid")
67+
}
68+
}

0 commit comments

Comments
 (0)