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 {
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) {

View file

@ -3,7 +3,6 @@ package fs
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
@ -57,29 +56,14 @@ 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
func splitBlkioStatLine(r rune) bool {
return r == ' ' || r == ':'
}
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)))
func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
var blkioStats []cgroups.BlkioStatEntry
f, err := os.Open(path)
if err != nil {
return nil, err
}
@ -88,34 +72,71 @@ func (s *blkioGroup) Stats(d *data) (map[string]int64, error) {
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)
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
}
paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v
case 2:
// this is the total line, skip
default:
return nil, ErrNotValidFormat
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
}
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) {
f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
if err != nil {
return "", 0, err
return blkioStats, nil
}
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 {
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 (
"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")
}

View file

@ -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
}
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
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.")
}

View file

@ -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

View file

@ -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) {

View file

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

View file

@ -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
}
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.
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
return nil
}

View file

@ -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",
}
for _, param := range params {
value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param))
value, err := getCgroupParamInt(path, "memory.usage_in_bytes")
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 (
"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")
}

View file

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

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

View file

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

View file

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