From 72e6e5ff7edc9c054e154897a4c547d89c082293 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Wed, 28 May 2014 00:01:08 +0000 Subject: [PATCH] Added a new method cgroups.GetStats() which will return a cgroups.Stats object which will contain all the available cgroup Stats. Remove old Stats interface in libcontainers cgroups package. Changed Stats to use unit64 instead of int64 to prevent integer overflow issues. Updated unit tests. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/fs/apply_raw.go | 17 ++- pkg/libcontainer/cgroups/fs/blkio.go | 129 ++++++++++-------- pkg/libcontainer/cgroups/fs/blkio_test.go | 69 +++++----- pkg/libcontainer/cgroups/fs/cpu.go | 24 +++- pkg/libcontainer/cgroups/fs/cpu_test.go | 33 +++-- pkg/libcontainer/cgroups/fs/cpuacct.go | 40 +++--- pkg/libcontainer/cgroups/fs/cpuset.go | 6 +- pkg/libcontainer/cgroups/fs/devices.go | 6 +- pkg/libcontainer/cgroups/fs/freezer.go | 49 +++---- pkg/libcontainer/cgroups/fs/memory.go | 33 +++-- pkg/libcontainer/cgroups/fs/memory_test.go | 20 +-- pkg/libcontainer/cgroups/fs/perf_event.go | 4 +- .../cgroups/fs/stats_test_util.go | 73 ++++++++++ pkg/libcontainer/cgroups/fs/test_util.go | 15 -- pkg/libcontainer/cgroups/fs/utils.go | 10 +- pkg/libcontainer/cgroups/stats.go | 32 +++-- 16 files changed, 329 insertions(+), 231 deletions(-) create mode 100644 pkg/libcontainer/cgroups/fs/stats_test_util.go diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index be500781ec..291d1e84b5 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -26,7 +26,7 @@ var ( type subsystem interface { Set(*data) error Remove(*data) error - Stats(*data) (map[string]int64, error) + GetStats(*data, *cgroups.Stats) error } type data struct { @@ -74,7 +74,8 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return d, nil } -func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, error) { +func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { + stats := cgroups.NewStats() cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") if err != nil { return nil, err @@ -94,13 +95,15 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, e root: cgroupRoot, cgroup: cgroup, c: c, - pid: pid, } - sys, exists := subsystems[subsystem] - if !exists { - return nil, fmt.Errorf("subsystem %s does not exist", subsystem) + + for _, sys := range subsystems { + if err := sys.GetStats(d, stats); err != nil { + return nil, err + } } - return sys.Stats(d) + + return stats, nil } func GetPids(c *cgroups.Cgroup) ([]int, error) { diff --git a/pkg/libcontainer/cgroups/fs/blkio.go b/pkg/libcontainer/cgroups/fs/blkio.go index 5cbef69f55..0c7a4e7b39 100644 --- a/pkg/libcontainer/cgroups/fs/blkio.go +++ b/pkg/libcontainer/cgroups/fs/blkio.go @@ -3,7 +3,6 @@ package fs import ( "bufio" "fmt" - "io/ioutil" "os" "path/filepath" "strconv" @@ -57,65 +56,87 @@ examples: 8:0 Total 0 Total 0 */ -func (s *blkioGroup) Stats(d *data) (map[string]int64, error) { - var ( - paramData = make(map[string]int64) - params = []string{ - "io_service_bytes_recursive", - "io_serviced_recursive", - "io_queued_recursive", - } - ) - path, err := d.path("blkio") - if err != nil { - return nil, err - } - - k, v, err := s.getSectors(path) - if err != nil { - return nil, err - } - paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v - - for _, param := range params { - f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param))) - if err != nil { - return nil, err - } - defer f.Close() - - sc := bufio.NewScanner(f) - for sc.Scan() { - // format: dev type amount - fields := strings.Fields(sc.Text()) - switch len(fields) { - case 3: - v, err := strconv.ParseInt(fields[2], 10, 64) - if err != nil { - return nil, err - } - paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v - case 2: - // this is the total line, skip - default: - return nil, ErrNotValidFormat - } - } - } - return paramData, nil +func splitBlkioStatLine(r rune) bool { + return r == ' ' || r == ':' } -func (s *blkioGroup) getSectors(path string) (string, int64, error) { - f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive")) +func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) { + var blkioStats []cgroups.BlkioStatEntry + f, err := os.Open(path) if err != nil { - return "", 0, err + return nil, err } defer f.Close() - data, err := ioutil.ReadAll(f) - if err != nil { - return "", 0, err + sc := bufio.NewScanner(f) + for sc.Scan() { + // format: dev type amount + fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine) + if len(fields) < 3 { + if len(fields) == 2 && fields[0] == "Total" { + // skip total line + continue + } else { + return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text()) + } + } + + v, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, err + } + major := v + + v, err = strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return nil, err + } + minor := v + + op := "" + valueField := 2 + if len(fields) == 4 { + op = fields[2] + valueField = 3 + } + v, err = strconv.ParseUint(fields[valueField], 10, 64) + if err != nil { + return nil, err + } + blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v}) } - return getCgroupParamKeyValue(string(data)) + + return blkioStats, nil +} + +func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error { + var blkioStats []cgroups.BlkioStatEntry + var err error + path, err := d.path("blkio") + if err != nil { + return err + } + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil { + return err + } + stats.BlkioStats.SectorsRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil { + return err + } + stats.BlkioStats.IoServiceBytesRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil { + return err + } + stats.BlkioStats.IoServicedRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil { + return err + } + stats.BlkioStats.IoQueuedRecursive = blkioStats + + return nil } diff --git a/pkg/libcontainer/cgroups/fs/blkio_test.go b/pkg/libcontainer/cgroups/fs/blkio_test.go index d0244ad716..d91a6479a9 100644 --- a/pkg/libcontainer/cgroups/fs/blkio_test.go +++ b/pkg/libcontainer/cgroups/fs/blkio_test.go @@ -2,14 +2,16 @@ package fs import ( "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) const ( sectorsRecursiveContents = `8:0 1024` serviceBytesRecursiveContents = `8:0 Read 100 -8:0 Write 400 -8:0 Sync 200 -8:0 Async 300 +8:0 Write 200 +8:0 Sync 300 +8:0 Async 500 8:0 Total 500 Total 500` servicedRecursiveContents = `8:0 Read 10 @@ -26,6 +28,12 @@ Total 50` Total 5` ) +var actualStats = *cgroups.NewStats() + +func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { + *blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op}) +} + func TestBlkioStats(t *testing.T) { helper := NewCgroupTestUtil("blkio", t) defer helper.cleanup() @@ -37,37 +45,34 @@ func TestBlkioStats(t *testing.T) { }) blkio := &blkioGroup{} - stats, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } // Verify expected stats. - expectedStats := map[string]int64{ - "blkio.sectors_recursive:8:0": 1024, + expectedStats := cgroups.BlkioStats{} + appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "") - // Serviced bytes. - "io_service_bytes_recursive:8:0:Read": 100, - "io_service_bytes_recursive:8:0:Write": 400, - "io_service_bytes_recursive:8:0:Sync": 200, - "io_service_bytes_recursive:8:0:Async": 300, - "io_service_bytes_recursive:8:0:Total": 500, + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total") - // Serviced requests. - "io_serviced_recursive:8:0:Read": 10, - "io_serviced_recursive:8:0:Write": 40, - "io_serviced_recursive:8:0:Sync": 20, - "io_serviced_recursive:8:0:Async": 30, - "io_serviced_recursive:8:0:Total": 50, + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total") - // Queued requests. - "io_queued_recursive:8:0:Read": 1, - "io_queued_recursive:8:0:Write": 4, - "io_queued_recursive:8:0:Sync": 2, - "io_queued_recursive:8:0:Async": 3, - "io_queued_recursive:8:0:Total": 5, - } - expectStats(t, expectedStats, stats) + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total") + + expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) } func TestBlkioStatsNoSectorsFile(t *testing.T) { @@ -80,7 +85,7 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -96,7 +101,7 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -112,7 +117,7 @@ func TestBlkioStatsNoServicedFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -128,7 +133,7 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -145,7 +150,7 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -162,7 +167,7 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } diff --git a/pkg/libcontainer/cgroups/fs/cpu.go b/pkg/libcontainer/cgroups/fs/cpu.go index ad3078b3b8..1c266f4a10 100644 --- a/pkg/libcontainer/cgroups/fs/cpu.go +++ b/pkg/libcontainer/cgroups/fs/cpu.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type cpuGroup struct { @@ -39,16 +41,15 @@ func (s *cpuGroup) Remove(d *data) error { return removePath(d.path("cpu")) } -func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { - paramData := make(map[string]int64) +func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("cpu") if err != nil { - return nil, err + return err } f, err := os.Open(filepath.Join(path, "cpu.stat")) if err != nil { - return nil, err + return err } defer f.Close() @@ -56,9 +57,18 @@ func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { - return nil, err + return err + } + switch t { + case "nr_periods": + stats.CpuStats.ThrottlingData.Periods = v + + case "nr_throttled": + stats.CpuStats.ThrottlingData.ThrottledPeriods = v + + case "throttled_time": + stats.CpuStats.ThrottlingData.ThrottledTime = v } - paramData[t] = v } - return paramData, nil + return nil } diff --git a/pkg/libcontainer/cgroups/fs/cpu_test.go b/pkg/libcontainer/cgroups/fs/cpu_test.go index cacf2f4ced..ad0674083a 100644 --- a/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/pkg/libcontainer/cgroups/fs/cpu_test.go @@ -1,31 +1,40 @@ package fs import ( + "fmt" "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) func TestCpuStats(t *testing.T) { helper := NewCgroupTestUtil("cpu", t) defer helper.cleanup() - cpuStatContent := `nr_periods 2000 - nr_throttled 200 - throttled_time 42424242424` + + const ( + kNrPeriods = 2000 + kNrThrottled = 200 + kThrottledTime = uint64(18446744073709551615) + ) + + cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n", + kNrPeriods, kNrThrottled, kThrottledTime) helper.writeFileContents(map[string]string{ "cpu.stat": cpuStatContent, }) cpu := &cpuGroup{} - stats, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } - expected_stats := map[string]int64{ - "nr_periods": 2000, - "nr_throttled": 200, - "throttled_time": 42424242424, - } - expectStats(t, expected_stats, stats) + expectedStats := cgroups.ThrottlingData{ + Periods: kNrPeriods, + ThrottledPeriods: kNrThrottled, + ThrottledTime: kThrottledTime} + + expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData) } func TestNoCpuStatFile(t *testing.T) { @@ -33,7 +42,7 @@ func TestNoCpuStatFile(t *testing.T) { defer helper.cleanup() cpu := &cpuGroup{} - _, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not.") } @@ -50,7 +59,7 @@ func TestInvalidCpuStat(t *testing.T) { }) cpu := &cpuGroup{} - _, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failed stat parsing.") } diff --git a/pkg/libcontainer/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go index c52049f3e9..36a50b8d4f 100644 --- a/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -15,8 +15,8 @@ import ( ) var ( - cpuCount = int64(runtime.NumCPU()) - clockTicks = int64(system.GetClockTicks()) + cpuCount = uint64(runtime.NumCPU()) + clockTicks = uint64(system.GetClockTicks()) ) type cpuacctGroup struct { @@ -34,34 +34,33 @@ func (s *cpuacctGroup) Remove(d *data) error { return removePath(d.path("cpuacct")) } -func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) { +func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { var ( - startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage int64 - percentage int64 - paramData = make(map[string]int64) + startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage uint64 + percentage uint64 ) path, err := d.path("cpuacct") if startCpu, err = s.getCpuUsage(d, path); err != nil { - return nil, err + return err } if startSystem, err = s.getSystemCpuUsage(d); err != nil { - return nil, err + return err } startUsageTime := time.Now() if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { - return nil, err + return err } // sample for 100ms time.Sleep(100 * time.Millisecond) if lastCpu, err = s.getCpuUsage(d, path); err != nil { - return nil, err + return err } if lastSystem, err = s.getSystemCpuUsage(d); err != nil { - return nil, err + return err } usageSampleDuration := time.Since(startUsageTime) if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { - return nil, err + return err } var ( @@ -74,15 +73,14 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) { } // NOTE: a percentage over 100% is valid for POSIX because that means the // processes is using multiple cores - paramData["percentage"] = percentage - + stats.CpuStats.CpuUsage.PercentUsage = percentage // Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time. - paramData["usage"] = deltaUsage / usageSampleDuration.Nanoseconds() - return paramData, nil + stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds()) + return nil } // TODO(vmarmol): Use cgroups stats. -func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { +func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) { f, err := os.Open("/proc/stat") if err != nil { @@ -99,9 +97,9 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { return 0, fmt.Errorf("invalid number of cpu fields") } - var total int64 + var total uint64 for _, i := range parts[1:8] { - v, err := strconv.ParseInt(i, 10, 64) + v, err := strconv.ParseUint(i, 10, 64) if err != nil { return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) } @@ -115,8 +113,8 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { return 0, fmt.Errorf("invalid stat format") } -func (s *cpuacctGroup) getCpuUsage(d *data, path string) (int64, error) { - cpuTotal := int64(0) +func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) { + cpuTotal := uint64(0) f, err := os.Open(filepath.Join(path, "cpuacct.stat")) if err != nil { return 0, err diff --git a/pkg/libcontainer/cgroups/fs/cpuset.go b/pkg/libcontainer/cgroups/fs/cpuset.go index af2dd528d0..c0b03c559e 100644 --- a/pkg/libcontainer/cgroups/fs/cpuset.go +++ b/pkg/libcontainer/cgroups/fs/cpuset.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type cpusetGroup struct { @@ -38,8 +40,8 @@ func (s *cpusetGroup) Remove(d *data) error { return removePath(d.path("cpuset")) } -func (s *cpusetGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { diff --git a/pkg/libcontainer/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go index 00fea608f9..569cbbf0d9 100644 --- a/pkg/libcontainer/cgroups/fs/devices.go +++ b/pkg/libcontainer/cgroups/fs/devices.go @@ -1,5 +1,7 @@ package fs +import "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + type devicesGroup struct { } @@ -55,6 +57,6 @@ func (s *devicesGroup) Remove(d *data) error { return removePath(d.path("devices")) } -func (s *devicesGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index 0738ec1f09..a9a27ef5a9 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -1,11 +1,8 @@ package fs import ( - "fmt" "io/ioutil" - "os" "path/filepath" - "strconv" "strings" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" @@ -35,39 +32,25 @@ func (s *freezerGroup) Remove(d *data) error { return removePath(d.path("freezer")) } -func (s *freezerGroup) Stats(d *data) (map[string]int64, error) { - var ( - paramData = make(map[string]int64) - params = []string{ - "parent_freezing", - "self_freezing", - // comment out right now because this is string "state", - } - ) +func getFreezerFileData(path string) (string, error) { + data, err := ioutil.ReadFile(path) + return strings.TrimSuffix(string(data), "\n"), err +} +func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("freezer") if err != nil { - return nil, err + return err } - - // TODO(vmarmol): This currently outputs nothing since the output is a string, fix. - for _, param := range params { - f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param))) - if err != nil { - return nil, err - } - defer f.Close() - - data, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - v, err := strconv.ParseInt(strings.TrimSuffix(string(data), "\n"), 10, 64) - if err != nil { - return nil, err - } - paramData[param] = v + var data string + if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil { + return err } - return paramData, nil + stats.FreezerStats.ParentState = data + if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil { + return err + } + stats.FreezerStats.SelfState = data + + return nil } diff --git a/pkg/libcontainer/cgroups/fs/memory.go b/pkg/libcontainer/cgroups/fs/memory.go index 9964f83767..f202b16009 100644 --- a/pkg/libcontainer/cgroups/fs/memory.go +++ b/pkg/libcontainer/cgroups/fs/memory.go @@ -2,10 +2,11 @@ package fs import ( "bufio" - "fmt" "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type memoryGroup struct { @@ -50,17 +51,16 @@ func (s *memoryGroup) Remove(d *data) error { return removePath(d.path("memory")) } -func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { - paramData := make(map[string]int64) +func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("memory") if err != nil { - return nil, err + return err } // Set stats from memory.stat. statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { - return nil, err + return err } defer statsFile.Close() @@ -68,23 +68,22 @@ func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { - return nil, err + return err } - paramData[t] = v + stats.MemoryStats.Stats[t] = v } // Set memory usage and max historical usage. - params := []string{ - "usage_in_bytes", - "max_usage_in_bytes", + value, err := getCgroupParamInt(path, "memory.usage_in_bytes") + if err != nil { + return err } - for _, param := range params { - value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param)) - if err != nil { - return nil, err - } - paramData[param] = value + stats.MemoryStats.Usage = value + value, err = getCgroupParamInt(path, "memory.max_usage_in_bytes") + if err != nil { + return err } + stats.MemoryStats.MaxUsage = value - return paramData, nil + return nil } diff --git a/pkg/libcontainer/cgroups/fs/memory_test.go b/pkg/libcontainer/cgroups/fs/memory_test.go index 190d437b1c..e7d2018712 100644 --- a/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/pkg/libcontainer/cgroups/fs/memory_test.go @@ -2,6 +2,8 @@ package fs import ( "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) const ( @@ -21,12 +23,12 @@ func TestMemoryStats(t *testing.T) { }) memory := &memoryGroup{} - stats, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } - expectedStats := map[string]int64{"cache": 512, "rss": 1024, "usage_in_bytes": 2048, "max_usage_in_bytes": 4096} - expectStats(t, expectedStats, stats) + expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) } func TestMemoryStatsNoStatFile(t *testing.T) { @@ -38,7 +40,7 @@ func TestMemoryStatsNoStatFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -53,7 +55,7 @@ func TestMemoryStatsNoUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -68,7 +70,7 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -84,7 +86,7 @@ func TestMemoryStatsBadStatFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -100,7 +102,7 @@ func TestMemoryStatsBadUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -116,7 +118,7 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } diff --git a/pkg/libcontainer/cgroups/fs/perf_event.go b/pkg/libcontainer/cgroups/fs/perf_event.go index 1cf1aeef12..1eb4df11b5 100644 --- a/pkg/libcontainer/cgroups/fs/perf_event.go +++ b/pkg/libcontainer/cgroups/fs/perf_event.go @@ -19,6 +19,6 @@ func (s *perfEventGroup) Remove(d *data) error { return removePath(d.path("perf_event")) } -func (s *perfEventGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } diff --git a/pkg/libcontainer/cgroups/fs/stats_test_util.go b/pkg/libcontainer/cgroups/fs/stats_test_util.go new file mode 100644 index 0000000000..bebd0cb3e3 --- /dev/null +++ b/pkg/libcontainer/cgroups/fs/stats_test_util.go @@ -0,0 +1,73 @@ +package fs + +import ( + "fmt" + "log" + "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" +) + +func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error { + if len(expected) != len(actual) { + return fmt.Errorf("blkioStatEntries length do not match") + } + for i, expValue := range expected { + actValue := actual[i] + if expValue != actValue { + return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue) + } + } + return nil +} + +func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) { + if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil { + log.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil { + log.Printf("blkio IoServicedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil { + log.Printf("blkio IoQueuedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil { + log.Printf("blkio SectorsRecursive do not match - %s\n", err) + t.Fail() + } +} + +func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) { + if expected != actual { + log.Printf("Expected throttling data %v but found %v\n", expected, actual) + t.Fail() + } +} + +func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) { + if expected.Usage != actual.Usage { + log.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage) + t.Fail() + } + if expected.MaxUsage != actual.MaxUsage { + log.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage) + t.Fail() + } + for key, expValue := range expected.Stats { + actValue, ok := actual.Stats[key] + if !ok { + log.Printf("Expected memory stat key %s not found\n", key) + t.Fail() + } + if expValue != actValue { + log.Printf("Expected memory stat value %d but found %d\n", expValue, actValue) + t.Fail() + } + } +} diff --git a/pkg/libcontainer/cgroups/fs/test_util.go b/pkg/libcontainer/cgroups/fs/test_util.go index 333386c5de..548870a8a3 100644 --- a/pkg/libcontainer/cgroups/fs/test_util.go +++ b/pkg/libcontainer/cgroups/fs/test_util.go @@ -8,7 +8,6 @@ package fs import ( "fmt" "io/ioutil" - "log" "os" "testing" ) @@ -59,17 +58,3 @@ func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { } } } - -// Expect the specified stats. -func expectStats(t *testing.T, expected, actual map[string]int64) { - for stat, expectedValue := range expected { - actualValue, ok := actual[stat] - if !ok { - log.Printf("Expected stat %s to exist: %s", stat, actual) - t.Fail() - } else if actualValue != expectedValue { - log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue) - t.Fail() - } - } -} diff --git a/pkg/libcontainer/cgroups/fs/utils.go b/pkg/libcontainer/cgroups/fs/utils.go index 7213b5d6a0..ff0586345d 100644 --- a/pkg/libcontainer/cgroups/fs/utils.go +++ b/pkg/libcontainer/cgroups/fs/utils.go @@ -16,11 +16,11 @@ var ( // Parses a cgroup param and returns as name, value // i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 -func getCgroupParamKeyValue(t string) (string, int64, error) { +func getCgroupParamKeyValue(t string) (string, uint64, error) { parts := strings.Fields(t) switch len(parts) { case 2: - value, err := strconv.ParseInt(parts[1], 10, 64) + value, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err) } @@ -31,10 +31,10 @@ func getCgroupParamKeyValue(t string) (string, int64, error) { } // Gets a single int64 value from the specified cgroup file. -func getCgroupParamInt(cgroupPath, cgroupFile string) (int64, error) { +func getCgroupParamInt(cgroupPath, cgroupFile string) (uint64, error) { contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) if err != nil { - return -1, err + return 0, err } - return strconv.ParseInt(strings.TrimSpace(string(contents)), 10, 64) + return strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) } diff --git a/pkg/libcontainer/cgroups/stats.go b/pkg/libcontainer/cgroups/stats.go index fbcd5dd234..7918d78fd0 100644 --- a/pkg/libcontainer/cgroups/stats.go +++ b/pkg/libcontainer/cgroups/stats.go @@ -2,18 +2,18 @@ package cgroups type ThrottlingData struct { // Number of periods with throttling active - Periods int64 `json:"periods,omitempty"` + Periods uint64 `json:"periods,omitempty"` // Number of periods when the container hit its throttling limit. - ThrottledPeriods int64 `json:"throttled_periods,omitempty"` + ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` // Aggregate time the container was throttled for in nanoseconds. - ThrottledTime int64 `json:"throttled_time,omitempty"` + ThrottledTime uint64 `json:"throttled_time,omitempty"` } type CpuUsage struct { // percentage of available CPUs currently being used. - PercentUsage int64 `json:"percent_usage,omitempty"` + PercentUsage uint64 `json:"percent_usage,omitempty"` // nanoseconds of cpu time consumed over the last 100 ms. - CurrentUsage int64 `json:"current_usage,omitempty"` + CurrentUsage uint64 `json:"current_usage,omitempty"` } type CpuStats struct { @@ -23,26 +23,27 @@ type CpuStats struct { type MemoryStats struct { // current res_counter usage for memory - Usage int64 `json:"usage,omitempty"` + Usage uint64 `json:"usage,omitempty"` // maximum usage ever recorded. - MaxUsage int64 `json:"max_usage,omitempty"` + MaxUsage uint64 `json:"max_usage,omitempty"` // TODO(vishh): Export these as stronger types. // all the stats exported via memory.stat. - Stats map[string]int64 `json:"stats,omitempty"` + Stats map[string]uint64 `json:"stats,omitempty"` } type BlkioStatEntry struct { - Major int64 `json:"major,omitempty"` - Minor int64 `json:"minor,omitempty"` + Major uint64 `json:"major,omitempty"` + Minor uint64 `json:"minor,omitempty"` Op string `json:"op,omitempty"` - Value int64 `json:"value,omitempty"` + Value uint64 `json:"value,omitempty"` } -type BlockioStats struct { +type BlkioStats struct { // number of bytes tranferred to and from the block device IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"` IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` + SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` } // TODO(Vishh): Remove freezer from stats since it does not logically belong in stats. @@ -54,6 +55,11 @@ type FreezerStats struct { type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` - BlockioStats BlockioStats `json:"blockio_stats,omitempty"` + BlkioStats BlkioStats `json:"blkio_stats,omitempty"` FreezerStats FreezerStats `json:"freezer_stats,omitempty"` } + +func NewStats() *Stats { + memoryStats := MemoryStats{Stats: make(map[string]uint64)} + return &Stats{MemoryStats: memoryStats} +}