Skip to content

Commit

Permalink
Merge pull request opencontainers#3972 from mythi/misc-stats
Browse files Browse the repository at this point in the history
libct/cg/stats: support misc for cgroup v2
  • Loading branch information
lifubang authored Oct 25, 2023
2 parents a68529c + f755c80 commit 1947d0c
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 1 deletion.
4 changes: 4 additions & 0 deletions libcontainer/cgroups/fs2/fs2.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
// misc (since kernel 5.13)
if err := statMisc(m.dirPath, st); err != nil && !os.IsNotExist(err) {
errs = append(errs, err)
}
if len(errs) > 0 && !m.config.Rootless {
return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
}
Expand Down
52 changes: 52 additions & 0 deletions libcontainer/cgroups/fs2/misc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package fs2

import (
"bufio"
"os"
"strings"

"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
)

func statMisc(dirPath string, stats *cgroups.Stats) error {
for _, file := range []string{"current", "events"} {
fd, err := cgroups.OpenFile(dirPath, "misc."+file, os.O_RDONLY)
if err != nil {
return err
}

s := bufio.NewScanner(fd)
for s.Scan() {
key, value, err := fscommon.ParseKeyValue(s.Text())
if err != nil {
fd.Close()
return err
}

key = strings.TrimSuffix(key, ".max")

if _, ok := stats.MiscStats[key]; !ok {
stats.MiscStats[key] = cgroups.MiscStats{}
}

tmp := stats.MiscStats[key]

switch file {
case "current":
tmp.Usage = value
case "events":
tmp.Events = value
}

stats.MiscStats[key] = tmp
}
fd.Close()

if err := s.Err(); err != nil {
return err
}
}

return nil
}
103 changes: 103 additions & 0 deletions libcontainer/cgroups/fs2/misc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package fs2

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/opencontainers/runc/libcontainer/cgroups"
)

const exampleMiscCurrentData = `res_a 123
res_b 456
res_c 42`

const exampleMiscEventsData = `res_a.max 1
res_b.max 2
res_c.max 3`

func TestStatMiscPodCgroupEmpty(t *testing.T) {
// We're using a fake cgroupfs.
cgroups.TestMode = true
fakeCgroupDir := t.TempDir()

// create empty misc.current and misc.events files to test the common case
// where no misc resource keys are available
for _, file := range []string{"misc.current", "misc.events"} {
if _, err := os.Create(filepath.Join(fakeCgroupDir, file)); err != nil {
t.Fatal(err)
}
}

gotStats := cgroups.NewStats()

err := statMisc(fakeCgroupDir, gotStats)
if err != nil {
t.Errorf("expected no error when statting empty misc.current/misc.events for cgroupv2, but got %#v", err)
}

if len(gotStats.MiscStats) != 0 {
t.Errorf("parsed cgroupv2 misc.* returns unexpected resources: got %#v but expected nothing", gotStats.MiscStats)
}
}

func TestStatMiscPodCgroupNotFound(t *testing.T) {
// We're using a fake cgroupfs.
cgroups.TestMode = true
fakeCgroupDir := t.TempDir()

// only write misc.current to ensure pod cgroup usage
// still reads misc.events.
statPath := filepath.Join(fakeCgroupDir, "misc.current")
if err := os.WriteFile(statPath, []byte(exampleMiscCurrentData), 0o644); err != nil {
t.Fatal(err)
}

gotStats := cgroups.NewStats()

// use a fake root path to mismatch the file we wrote.
// this triggers the non-root path which should fail to find misc.events.
err := statMisc(fakeCgroupDir, gotStats)
if err == nil {
t.Errorf("expected error when statting misc.current for cgroupv2 root, but was nil")
}

if !strings.Contains(err.Error(), "misc.events: no such file or directory") {
t.Errorf("expected error to contain 'misc.events: no such file or directory', but was %s", err.Error())
}
}

func TestStatMiscPodCgroup(t *testing.T) {
// We're using a fake cgroupfs.
cgroups.TestMode = true
fakeCgroupDir := t.TempDir()

currentPath := filepath.Join(fakeCgroupDir, "misc.current")
if err := os.WriteFile(currentPath, []byte(exampleMiscCurrentData), 0o644); err != nil {
t.Fatal(err)
}

eventsPath := filepath.Join(fakeCgroupDir, "misc.events")
if err := os.WriteFile(eventsPath, []byte(exampleMiscEventsData), 0o644); err != nil {
t.Fatal(err)
}

gotStats := cgroups.NewStats()

// use a fake root path to trigger the pod cgroup lookup.
err := statMisc(fakeCgroupDir, gotStats)
if err != nil {
t.Errorf("expected no error when statting misc for cgroupv2 root, but got %#+v", err)
}

// make sure all res_* from exampleMisc*Data are returned
if len(gotStats.MiscStats) != 3 {
t.Errorf("parsed cgroupv2 misc doesn't return all expected resources: \ngot %#v\nexpected %#v\n", len(gotStats.MiscStats), 3)
}

var expectedUsageBytes uint64 = 42
if gotStats.MiscStats["res_c"].Usage != expectedUsageBytes {
t.Errorf("parsed cgroupv2 misc.current for res_c doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MiscStats["res_c"].Usage, expectedUsageBytes)
}
}
12 changes: 11 additions & 1 deletion libcontainer/cgroups/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ type RdmaStats struct {
RdmaCurrent []RdmaEntry `json:"rdma_current,omitempty"`
}

type MiscStats struct {
// current resource usage for a key in misc
Usage uint64 `json:"usage,omitempty"`
// number of times the resource usage was about to go over the max boundary
Events uint64 `json:"events,omitempty"`
}

type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"`
CPUSetStats CPUSetStats `json:"cpuset_stats,omitempty"`
Expand All @@ -179,10 +186,13 @@ type Stats struct {
// the map is in the format "size of hugepage: stats of the hugepage"
HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
RdmaStats RdmaStats `json:"rdma_stats,omitempty"`
// the map is in the format "misc resource name: stats of the key"
MiscStats map[string]MiscStats `json:"misc_stats,omitempty"`
}

func NewStats() *Stats {
memoryStats := MemoryStats{Stats: make(map[string]uint64)}
hugetlbStats := make(map[string]HugetlbStats)
return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats}
miscStats := make(map[string]MiscStats)
return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats, MiscStats: miscStats}
}

0 comments on commit 1947d0c

Please sign in to comment.