diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go index be50078..291d1e8 100644 --- a/libcontainer/cgroups/fs/apply_raw.go +++ b/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/libcontainer/cgroups/fs/blkio.go b/libcontainer/cgroups/fs/blkio.go index 5cbef69..0c7a4e7 100644 --- a/libcontainer/cgroups/fs/blkio.go +++ b/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/libcontainer/cgroups/fs/blkio_test.go b/libcontainer/cgroups/fs/blkio_test.go index d0244ad..d91a647 100644 --- a/libcontainer/cgroups/fs/blkio_test.go +++ b/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/libcontainer/cgroups/fs/cpu.go b/libcontainer/cgroups/fs/cpu.go index ad3078b..1c266f4 100644 --- a/libcontainer/cgroups/fs/cpu.go +++ b/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/libcontainer/cgroups/fs/cpu_test.go b/libcontainer/cgroups/fs/cpu_test.go index cacf2f4..ad06740 100644 --- a/libcontainer/cgroups/fs/cpu_test.go +++ b/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/libcontainer/cgroups/fs/cpuacct.go b/libcontainer/cgroups/fs/cpuacct.go index c52049f..36a50b8 100644 --- a/libcontainer/cgroups/fs/cpuacct.go +++ b/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/libcontainer/cgroups/fs/cpuset.go b/libcontainer/cgroups/fs/cpuset.go index af2dd52..c0b03c5 100644 --- a/libcontainer/cgroups/fs/cpuset.go +++ b/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/libcontainer/cgroups/fs/devices.go b/libcontainer/cgroups/fs/devices.go index 00fea60..569cbbf 100644 --- a/libcontainer/cgroups/fs/devices.go +++ b/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/libcontainer/cgroups/fs/freezer.go b/libcontainer/cgroups/fs/freezer.go index 0738ec1..a9a27ef 100644 --- a/libcontainer/cgroups/fs/freezer.go +++ b/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/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go index 9964f83..f202b16 100644 --- a/libcontainer/cgroups/fs/memory.go +++ b/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/libcontainer/cgroups/fs/memory_test.go b/libcontainer/cgroups/fs/memory_test.go index 190d437..e7d2018 100644 --- a/libcontainer/cgroups/fs/memory_test.go +++ b/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/libcontainer/cgroups/fs/perf_event.go b/libcontainer/cgroups/fs/perf_event.go index 1cf1aee..1eb4df1 100644 --- a/libcontainer/cgroups/fs/perf_event.go +++ b/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/libcontainer/cgroups/fs/stats_test_util.go b/libcontainer/cgroups/fs/stats_test_util.go new file mode 100644 index 0000000..bebd0cb --- /dev/null +++ b/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/libcontainer/cgroups/fs/test_util.go b/libcontainer/cgroups/fs/test_util.go index 333386c..548870a 100644 --- a/libcontainer/cgroups/fs/test_util.go +++ b/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/libcontainer/cgroups/fs/utils.go b/libcontainer/cgroups/fs/utils.go index 7213b5d..ff05863 100644 --- a/libcontainer/cgroups/fs/utils.go +++ b/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/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go index fbcd5dd..7918d78 100644 --- a/libcontainer/cgroups/stats.go +++ b/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} +}