Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cgroup2: map io.stats to v1 blkio.stats correctly #2968

Merged
merged 3 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions libcontainer/cgroups/fs2/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strconv"
"strings"

"github.com/sirupsen/logrus"

"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
"github.com/opencontainers/runc/libcontainer/configs"
Expand Down Expand Up @@ -88,22 +90,22 @@ func readCgroup2MapFile(dirPath string, name string) (map[string][]string, error
}

func statIo(dirPath string, stats *cgroups.Stats) error {
// more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
var ioServiceBytesRecursive []cgroups.BlkioStatEntry
values, err := readCgroup2MapFile(dirPath, "io.stat")
if err != nil {
return err
}
// more details on the io.stat file format: https://www.kernel.org/doc/Documentation/cgroup-v2.txt
var parsedStats cgroups.BlkioStats
for k, v := range values {
d := strings.Split(k, ":")
if len(d) != 2 {
continue
}
major, err := strconv.ParseUint(d[0], 10, 0)
major, err := strconv.ParseUint(d[0], 10, 64)
if err != nil {
return err
}
minor, err := strconv.ParseUint(d[1], 10, 0)
minor, err := strconv.ParseUint(d[1], 10, 64)
if err != nil {
return err
}
Expand All @@ -115,15 +117,32 @@ func statIo(dirPath string, stats *cgroups.Stats) error {
}
op := d[0]

// Accommodate the cgroup v1 naming
// Map to the cgroupv1 naming and layout (in separate tables).
var targetTable *[]cgroups.BlkioStatEntry
switch op {
// Equivalent to cgroupv1's blkio.io_service_bytes.
case "rbytes":
op = "Read"
targetTable = &parsedStats.IoServiceBytesRecursive
case "wbytes":
op = "Write"
targetTable = &parsedStats.IoServiceBytesRecursive
// Equivalent to cgroupv1's blkio.io_serviced.
case "rios":
op = "Read"
targetTable = &parsedStats.IoServicedRecursive
case "wios":
op = "Write"
targetTable = &parsedStats.IoServicedRecursive
default:
// Skip over entries we cannot map to cgroupv1 stats for now.
// In the future we should expand the stats struct to include
// them.
logrus.Debugf("cgroupv2 io stats: skipping over unmappable %s entry", item)
continue
}

value, err := strconv.ParseUint(d[1], 10, 0)
value, err := strconv.ParseUint(d[1], 10, 64)
if err != nil {
return err
}
Expand All @@ -134,9 +153,9 @@ func statIo(dirPath string, stats *cgroups.Stats) error {
Minor: minor,
Value: value,
}
ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
*targetTable = append(*targetTable, entry)
}
}
stats.BlkioStats = cgroups.BlkioStats{IoServiceBytesRecursive: ioServiceBytesRecursive}
stats.BlkioStats = parsedStats
return nil
}
87 changes: 87 additions & 0 deletions libcontainer/cgroups/fs2/io_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package fs2

import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"

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

const exampleIoStatData = `254:1 rbytes=6901432320 wbytes=14245535744 rios=263278 wios=248603 dbytes=0 dios=0
254:0 rbytes=2702336 wbytes=0 rios=97 wios=0 dbytes=0 dios=0
259:0 rbytes=6911345664 wbytes=14245536256 rios=264538 wios=244914 dbytes=530485248 dios=2`

var exampleIoStatsParsed = cgroups.BlkioStats{
IoServiceBytesRecursive: []cgroups.BlkioStatEntry{
{Major: 254, Minor: 1, Value: 6901432320, Op: "Read"},
{Major: 254, Minor: 1, Value: 14245535744, Op: "Write"},
{Major: 254, Minor: 0, Value: 2702336, Op: "Read"},
{Major: 254, Minor: 0, Value: 0, Op: "Write"},
{Major: 259, Minor: 0, Value: 6911345664, Op: "Read"},
{Major: 259, Minor: 0, Value: 14245536256, Op: "Write"},
},
IoServicedRecursive: []cgroups.BlkioStatEntry{
{Major: 254, Minor: 1, Value: 263278, Op: "Read"},
{Major: 254, Minor: 1, Value: 248603, Op: "Write"},
{Major: 254, Minor: 0, Value: 97, Op: "Read"},
{Major: 254, Minor: 0, Value: 0, Op: "Write"},
{Major: 259, Minor: 0, Value: 264538, Op: "Read"},
{Major: 259, Minor: 0, Value: 244914, Op: "Write"},
},
}

func lessBlkioStatEntry(a, b cgroups.BlkioStatEntry) bool {
if a.Major != b.Major {
return a.Major < b.Major
}
if a.Minor != b.Minor {
return a.Minor < b.Minor
}
if a.Op != b.Op {
return a.Op < b.Op
}
return a.Value < b.Value
}

func sortBlkioStats(stats *cgroups.BlkioStats) {
for _, table := range []*[]cgroups.BlkioStatEntry{
&stats.IoServicedRecursive,
&stats.IoServiceBytesRecursive,
} {
sort.SliceStable(*table, func(i, j int) bool { return lessBlkioStatEntry((*table)[i], (*table)[j]) })
}
}

func TestStatIo(t *testing.T) {
// We're using a fake cgroupfs.
fscommon.TestMode = true

fakeCgroupDir, err := ioutil.TempDir("", "runc-stat-io-test.*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(fakeCgroupDir)
statPath := filepath.Join(fakeCgroupDir, "io.stat")

if err := ioutil.WriteFile(statPath, []byte(exampleIoStatData), 0644); err != nil {
t.Fatal(err)
}

var gotStats cgroups.Stats
if err := statIo(fakeCgroupDir, &gotStats); err != nil {
t.Error(err)
}

// Sort the output since statIo uses a map internally.
sortBlkioStats(&gotStats.BlkioStats)
sortBlkioStats(&exampleIoStatsParsed)

if !reflect.DeepEqual(gotStats.BlkioStats, exampleIoStatsParsed) {
t.Errorf("parsed cgroupv2 io.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.BlkioStats, exampleIoStatsParsed)
}
}