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 <vishnuk@google.com> (github: vishh)
This commit is contained in:
Vishnu Kannan 2014-05-28 00:01:08 +00:00
parent c22a0a3297
commit c7135d73d3
16 changed files with 329 additions and 231 deletions

View file

@ -26,7 +26,7 @@ var (
type subsystem interface { type subsystem interface {
Set(*data) error Set(*data) error
Remove(*data) error Remove(*data) error
Stats(*data) (map[string]int64, error) GetStats(*data, *cgroups.Stats) error
} }
type data struct { type data struct {
@ -74,7 +74,8 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
return d, nil 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") cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
if err != nil { if err != nil {
return nil, err return nil, err
@ -94,13 +95,15 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, e
root: cgroupRoot, root: cgroupRoot,
cgroup: cgroup, cgroup: cgroup,
c: c, c: c,
pid: pid,
} }
sys, exists := subsystems[subsystem]
if !exists { for _, sys := range subsystems {
return nil, fmt.Errorf("subsystem %s does not exist", subsystem) 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) { func GetPids(c *cgroups.Cgroup) ([]int, error) {

View file

@ -3,7 +3,6 @@ package fs
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -57,29 +56,14 @@ examples:
8:0 Total 0 8:0 Total 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") func splitBlkioStatLine(r rune) bool {
if err != nil { return r == ' ' || r == ':'
return nil, err
} }
k, v, err := s.getSectors(path) func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
if err != nil { var blkioStats []cgroups.BlkioStatEntry
return nil, err f, err := os.Open(path)
}
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 { if err != nil {
return nil, err return nil, err
} }
@ -88,34 +72,71 @@ func (s *blkioGroup) Stats(d *data) (map[string]int64, error) {
sc := bufio.NewScanner(f) sc := bufio.NewScanner(f)
for sc.Scan() { for sc.Scan() {
// format: dev type amount // format: dev type amount
fields := strings.Fields(sc.Text()) fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
switch len(fields) { if len(fields) < 3 {
case 3: if len(fields) == 2 && fields[0] == "Total" {
v, err := strconv.ParseInt(fields[2], 10, 64) // 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 { if err != nil {
return nil, err return nil, err
} }
paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v major := v
case 2:
// this is the total line, skip v, err = strconv.ParseUint(fields[1], 10, 64)
default: if err != nil {
return nil, ErrNotValidFormat 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
} }
return paramData, nil blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
} }
func (s *blkioGroup) getSectors(path string) (string, int64, error) { return blkioStats, nil
f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
if err != nil {
return "", 0, err
} }
defer f.Close()
data, err := ioutil.ReadAll(f) 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 { if err != nil {
return "", 0, err return err
} }
return getCgroupParamKeyValue(string(data))
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
} }

View file

@ -2,14 +2,16 @@ package fs
import ( import (
"testing" "testing"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
const ( const (
sectorsRecursiveContents = `8:0 1024` sectorsRecursiveContents = `8:0 1024`
serviceBytesRecursiveContents = `8:0 Read 100 serviceBytesRecursiveContents = `8:0 Read 100
8:0 Write 400 8:0 Write 200
8:0 Sync 200 8:0 Sync 300
8:0 Async 300 8:0 Async 500
8:0 Total 500 8:0 Total 500
Total 500` Total 500`
servicedRecursiveContents = `8:0 Read 10 servicedRecursiveContents = `8:0 Read 10
@ -26,6 +28,12 @@ Total 50`
Total 5` 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) { func TestBlkioStats(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t) helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup() defer helper.cleanup()
@ -37,37 +45,34 @@ func TestBlkioStats(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
stats, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Verify expected stats. // Verify expected stats.
expectedStats := map[string]int64{ expectedStats := cgroups.BlkioStats{}
"blkio.sectors_recursive:8:0": 1024, appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "")
// Serviced bytes. appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read")
"io_service_bytes_recursive:8:0:Read": 100, appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write")
"io_service_bytes_recursive:8:0:Write": 400, appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync")
"io_service_bytes_recursive:8:0:Sync": 200, appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async")
"io_service_bytes_recursive:8:0:Async": 300, appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total")
"io_service_bytes_recursive:8:0:Total": 500,
// Serviced requests. appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read")
"io_serviced_recursive:8:0:Read": 10, appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write")
"io_serviced_recursive:8:0:Write": 40, appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync")
"io_serviced_recursive:8:0:Sync": 20, appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async")
"io_serviced_recursive:8:0:Async": 30, appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total")
"io_serviced_recursive:8:0:Total": 50,
// Queued requests. appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read")
"io_queued_recursive:8:0:Read": 1, appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write")
"io_queued_recursive:8:0:Write": 4, appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync")
"io_queued_recursive:8:0:Sync": 2, appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async")
"io_queued_recursive:8:0:Async": 3, appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total")
"io_queued_recursive:8:0:Total": 5,
} expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
expectStats(t, expectedStats, stats)
} }
func TestBlkioStatsNoSectorsFile(t *testing.T) { func TestBlkioStatsNoSectorsFile(t *testing.T) {
@ -80,7 +85,7 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }
@ -96,7 +101,7 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }
@ -112,7 +117,7 @@ func TestBlkioStatsNoServicedFile(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }
@ -128,7 +133,7 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }
@ -145,7 +150,7 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }
@ -162,7 +167,7 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
}) })
blkio := &blkioGroup{} blkio := &blkioGroup{}
_, err := blkio.Stats(helper.CgroupData) err := blkio.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not") t.Fatal("Expected to fail, but did not")
} }

View file

@ -5,6 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
type cpuGroup struct { type cpuGroup struct {
@ -39,16 +41,15 @@ func (s *cpuGroup) Remove(d *data) error {
return removePath(d.path("cpu")) return removePath(d.path("cpu"))
} }
func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error {
paramData := make(map[string]int64)
path, err := d.path("cpu") path, err := d.path("cpu")
if err != nil { if err != nil {
return nil, err return err
} }
f, err := os.Open(filepath.Join(path, "cpu.stat")) f, err := os.Open(filepath.Join(path, "cpu.stat"))
if err != nil { if err != nil {
return nil, err return err
} }
defer f.Close() defer f.Close()
@ -56,9 +57,18 @@ func (s *cpuGroup) Stats(d *data) (map[string]int64, error) {
for sc.Scan() { for sc.Scan() {
t, v, err := getCgroupParamKeyValue(sc.Text()) t, v, err := getCgroupParamKeyValue(sc.Text())
if err != nil { if err != nil {
return nil, err return err
} }
paramData[t] = v 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
} }
return paramData, nil }
return nil
} }

View file

@ -1,31 +1,40 @@
package fs package fs
import ( import (
"fmt"
"testing" "testing"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
func TestCpuStats(t *testing.T) { func TestCpuStats(t *testing.T) {
helper := NewCgroupTestUtil("cpu", t) helper := NewCgroupTestUtil("cpu", t)
defer helper.cleanup() defer helper.cleanup()
cpuStatContent := `nr_periods 2000
nr_throttled 200 const (
throttled_time 42424242424` 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{ helper.writeFileContents(map[string]string{
"cpu.stat": cpuStatContent, "cpu.stat": cpuStatContent,
}) })
cpu := &cpuGroup{} cpu := &cpuGroup{}
stats, err := cpu.Stats(helper.CgroupData) err := cpu.GetStats(helper.CgroupData, &actualStats)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected_stats := map[string]int64{ expectedStats := cgroups.ThrottlingData{
"nr_periods": 2000, Periods: kNrPeriods,
"nr_throttled": 200, ThrottledPeriods: kNrThrottled,
"throttled_time": 42424242424, ThrottledTime: kThrottledTime}
}
expectStats(t, expected_stats, stats) expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData)
} }
func TestNoCpuStatFile(t *testing.T) { func TestNoCpuStatFile(t *testing.T) {
@ -33,7 +42,7 @@ func TestNoCpuStatFile(t *testing.T) {
defer helper.cleanup() defer helper.cleanup()
cpu := &cpuGroup{} cpu := &cpuGroup{}
_, err := cpu.Stats(helper.CgroupData) err := cpu.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected to fail, but did not.") t.Fatal("Expected to fail, but did not.")
} }
@ -50,7 +59,7 @@ func TestInvalidCpuStat(t *testing.T) {
}) })
cpu := &cpuGroup{} cpu := &cpuGroup{}
_, err := cpu.Stats(helper.CgroupData) err := cpu.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failed stat parsing.") t.Fatal("Expected failed stat parsing.")
} }

View file

@ -15,8 +15,8 @@ import (
) )
var ( var (
cpuCount = int64(runtime.NumCPU()) cpuCount = uint64(runtime.NumCPU())
clockTicks = int64(system.GetClockTicks()) clockTicks = uint64(system.GetClockTicks())
) )
type cpuacctGroup struct { type cpuacctGroup struct {
@ -34,34 +34,33 @@ func (s *cpuacctGroup) Remove(d *data) error {
return removePath(d.path("cpuacct")) 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 ( var (
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage int64 startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage uint64
percentage int64 percentage uint64
paramData = make(map[string]int64)
) )
path, err := d.path("cpuacct") path, err := d.path("cpuacct")
if startCpu, err = s.getCpuUsage(d, path); err != nil { if startCpu, err = s.getCpuUsage(d, path); err != nil {
return nil, err return err
} }
if startSystem, err = s.getSystemCpuUsage(d); err != nil { if startSystem, err = s.getSystemCpuUsage(d); err != nil {
return nil, err return err
} }
startUsageTime := time.Now() startUsageTime := time.Now()
if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
return nil, err return err
} }
// sample for 100ms // sample for 100ms
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
if lastCpu, err = s.getCpuUsage(d, path); err != nil { if lastCpu, err = s.getCpuUsage(d, path); err != nil {
return nil, err return err
} }
if lastSystem, err = s.getSystemCpuUsage(d); err != nil { if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
return nil, err return err
} }
usageSampleDuration := time.Since(startUsageTime) usageSampleDuration := time.Since(startUsageTime)
if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
return nil, err return err
} }
var ( 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 // NOTE: a percentage over 100% is valid for POSIX because that means the
// processes is using multiple cores // 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. // Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
paramData["usage"] = deltaUsage / usageSampleDuration.Nanoseconds() stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
return paramData, nil return nil
} }
// TODO(vmarmol): Use cgroups stats. // 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") f, err := os.Open("/proc/stat")
if err != nil { if err != nil {
@ -99,9 +97,9 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) {
return 0, fmt.Errorf("invalid number of cpu fields") return 0, fmt.Errorf("invalid number of cpu fields")
} }
var total int64 var total uint64
for _, i := range parts[1:8] { for _, i := range parts[1:8] {
v, err := strconv.ParseInt(i, 10, 64) v, err := strconv.ParseUint(i, 10, 64)
if err != nil { if err != nil {
return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) 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") return 0, fmt.Errorf("invalid stat format")
} }
func (s *cpuacctGroup) getCpuUsage(d *data, path string) (int64, error) { func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) {
cpuTotal := int64(0) cpuTotal := uint64(0)
f, err := os.Open(filepath.Join(path, "cpuacct.stat")) f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
if err != nil { if err != nil {
return 0, err return 0, err

View file

@ -6,6 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
type cpusetGroup struct { type cpusetGroup struct {
@ -38,8 +40,8 @@ func (s *cpusetGroup) Remove(d *data) error {
return removePath(d.path("cpuset")) return removePath(d.path("cpuset"))
} }
func (s *cpusetGroup) Stats(d *data) (map[string]int64, error) { func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error {
return nil, ErrNotSupportStat return nil
} }
func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {

View file

@ -1,5 +1,7 @@
package fs package fs
import "github.com/dotcloud/docker/pkg/libcontainer/cgroups"
type devicesGroup struct { type devicesGroup struct {
} }
@ -55,6 +57,6 @@ func (s *devicesGroup) Remove(d *data) error {
return removePath(d.path("devices")) return removePath(d.path("devices"))
} }
func (s *devicesGroup) Stats(d *data) (map[string]int64, error) { func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error {
return nil, ErrNotSupportStat return nil
} }

View file

@ -1,11 +1,8 @@
package fs package fs
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups" "github.com/dotcloud/docker/pkg/libcontainer/cgroups"
@ -35,39 +32,25 @@ func (s *freezerGroup) Remove(d *data) error {
return removePath(d.path("freezer")) return removePath(d.path("freezer"))
} }
func (s *freezerGroup) Stats(d *data) (map[string]int64, error) { func getFreezerFileData(path string) (string, error) {
var ( data, err := ioutil.ReadFile(path)
paramData = make(map[string]int64) return strings.TrimSuffix(string(data), "\n"), err
params = []string{
"parent_freezing",
"self_freezing",
// comment out right now because this is string "state",
} }
)
func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("freezer") path, err := d.path("freezer")
if err != nil { if err != nil {
return nil, err return err
} }
var data string
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
return err
}
stats.FreezerStats.ParentState = data
if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil {
return err
}
stats.FreezerStats.SelfState = data
// TODO(vmarmol): This currently outputs nothing since the output is a string, fix. return nil
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
}
return paramData, nil
} }

View file

@ -2,10 +2,11 @@ package fs
import ( import (
"bufio" "bufio"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
type memoryGroup struct { type memoryGroup struct {
@ -50,17 +51,16 @@ func (s *memoryGroup) Remove(d *data) error {
return removePath(d.path("memory")) return removePath(d.path("memory"))
} }
func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
paramData := make(map[string]int64)
path, err := d.path("memory") path, err := d.path("memory")
if err != nil { if err != nil {
return nil, err return err
} }
// Set stats from memory.stat. // Set stats from memory.stat.
statsFile, err := os.Open(filepath.Join(path, "memory.stat")) statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
if err != nil { if err != nil {
return nil, err return err
} }
defer statsFile.Close() defer statsFile.Close()
@ -68,23 +68,22 @@ func (s *memoryGroup) Stats(d *data) (map[string]int64, error) {
for sc.Scan() { for sc.Scan() {
t, v, err := getCgroupParamKeyValue(sc.Text()) t, v, err := getCgroupParamKeyValue(sc.Text())
if err != nil { if err != nil {
return nil, err return err
} }
paramData[t] = v stats.MemoryStats.Stats[t] = v
} }
// Set memory usage and max historical usage. // Set memory usage and max historical usage.
params := []string{ value, err := getCgroupParamInt(path, "memory.usage_in_bytes")
"usage_in_bytes",
"max_usage_in_bytes",
}
for _, param := range params {
value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param))
if err != nil { if err != nil {
return nil, err return 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
} }

View file

@ -2,6 +2,8 @@ package fs
import ( import (
"testing" "testing"
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
) )
const ( const (
@ -21,12 +23,12 @@ func TestMemoryStats(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
stats, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectedStats := map[string]int64{"cache": 512, "rss": 1024, "usage_in_bytes": 2048, "max_usage_in_bytes": 4096} expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
expectStats(t, expectedStats, stats) expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
} }
func TestMemoryStatsNoStatFile(t *testing.T) { func TestMemoryStatsNoStatFile(t *testing.T) {
@ -38,7 +40,7 @@ func TestMemoryStatsNoStatFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }
@ -53,7 +55,7 @@ func TestMemoryStatsNoUsageFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }
@ -68,7 +70,7 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }
@ -84,7 +86,7 @@ func TestMemoryStatsBadStatFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }
@ -100,7 +102,7 @@ func TestMemoryStatsBadUsageFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }
@ -116,7 +118,7 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
}) })
memory := &memoryGroup{} memory := &memoryGroup{}
_, err := memory.Stats(helper.CgroupData) err := memory.GetStats(helper.CgroupData, &actualStats)
if err == nil { if err == nil {
t.Fatal("Expected failure") t.Fatal("Expected failure")
} }

View file

@ -19,6 +19,6 @@ func (s *perfEventGroup) Remove(d *data) error {
return removePath(d.path("perf_event")) return removePath(d.path("perf_event"))
} }
func (s *perfEventGroup) Stats(d *data) (map[string]int64, error) { func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error {
return nil, ErrNotSupportStat return nil
} }

View file

@ -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()
}
}
}

View file

@ -8,7 +8,6 @@ package fs
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"testing" "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()
}
}
}

View file

@ -16,11 +16,11 @@ var (
// Parses a cgroup param and returns as name, value // Parses a cgroup param and returns as name, value
// i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 // 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) parts := strings.Fields(t)
switch len(parts) { switch len(parts) {
case 2: case 2:
value, err := strconv.ParseInt(parts[1], 10, 64) value, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil { if err != nil {
return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err) 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. // 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)) contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
if err != nil { 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)
} }

View file

@ -2,18 +2,18 @@ package cgroups
type ThrottlingData struct { type ThrottlingData struct {
// Number of periods with throttling active // 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. // 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. // Aggregate time the container was throttled for in nanoseconds.
ThrottledTime int64 `json:"throttled_time,omitempty"` ThrottledTime uint64 `json:"throttled_time,omitempty"`
} }
type CpuUsage struct { type CpuUsage struct {
// percentage of available CPUs currently being used. // 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. // 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 { type CpuStats struct {
@ -23,26 +23,27 @@ type CpuStats struct {
type MemoryStats struct { type MemoryStats struct {
// current res_counter usage for memory // current res_counter usage for memory
Usage int64 `json:"usage,omitempty"` Usage uint64 `json:"usage,omitempty"`
// maximum usage ever recorded. // maximum usage ever recorded.
MaxUsage int64 `json:"max_usage,omitempty"` MaxUsage uint64 `json:"max_usage,omitempty"`
// TODO(vishh): Export these as stronger types. // TODO(vishh): Export these as stronger types.
// all the stats exported via memory.stat. // all the stats exported via memory.stat.
Stats map[string]int64 `json:"stats,omitempty"` Stats map[string]uint64 `json:"stats,omitempty"`
} }
type BlkioStatEntry struct { type BlkioStatEntry struct {
Major int64 `json:"major,omitempty"` Major uint64 `json:"major,omitempty"`
Minor int64 `json:"minor,omitempty"` Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,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 // number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,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. // TODO(Vishh): Remove freezer from stats since it does not logically belong in stats.
@ -54,6 +55,11 @@ type FreezerStats struct {
type Stats struct { type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"` CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_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"` FreezerStats FreezerStats `json:"freezer_stats,omitempty"`
} }
func NewStats() *Stats {
memoryStats := MemoryStats{Stats: make(map[string]uint64)}
return &Stats{MemoryStats: memoryStats}
}