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:
parent
c22a0a3297
commit
c7135d73d3
16 changed files with 329 additions and 231 deletions
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
73
libcontainer/cgroups/fs/stats_test_util.go
Normal file
73
libcontainer/cgroups/fs/stats_test_util.go
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue