Merge pull request #6096 from vishh/stats_strongtype
Replaced fs.Stats() interface with fs.GetStats() which returns a cgroups.Stats strong type
This commit is contained in:
commit
887ab77922
|
@ -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,65 +56,87 @@ examples:
|
|||
8:0 Total 0
|
||||
Total 0
|
||||
*/
|
||||
func (s *blkioGroup) Stats(d *data) (map[string]int64, error) {
|
||||
var (
|
||||
paramData = make(map[string]int64)
|
||||
params = []string{
|
||||
"io_service_bytes_recursive",
|
||||
"io_serviced_recursive",
|
||||
"io_queued_recursive",
|
||||
}
|
||||
)
|
||||
|
||||
path, err := d.path("blkio")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k, v, err := s.getSectors(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v
|
||||
|
||||
for _, param := range params {
|
||||
f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
// format: dev type amount
|
||||
fields := strings.Fields(sc.Text())
|
||||
switch len(fields) {
|
||||
case 3:
|
||||
v, err := strconv.ParseInt(fields[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v
|
||||
case 2:
|
||||
// this is the total line, skip
|
||||
default:
|
||||
return nil, ErrNotValidFormat
|
||||
}
|
||||
}
|
||||
}
|
||||
return paramData, nil
|
||||
func splitBlkioStatLine(r rune) bool {
|
||||
return r == ' ' || r == ':'
|
||||
}
|
||||
|
||||
func (s *blkioGroup) getSectors(path string) (string, int64, error) {
|
||||
f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
|
||||
func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
|
||||
var blkioStats []cgroups.BlkioStatEntry
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
// format: dev type amount
|
||||
fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
|
||||
if len(fields) < 3 {
|
||||
if len(fields) == 2 && fields[0] == "Total" {
|
||||
// skip total line
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
|
||||
}
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(fields[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
major := v
|
||||
|
||||
v, err = strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minor := v
|
||||
|
||||
op := ""
|
||||
valueField := 2
|
||||
if len(fields) == 4 {
|
||||
op = fields[2]
|
||||
valueField = 3
|
||||
}
|
||||
v, err = strconv.ParseUint(fields[valueField], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
|
||||
}
|
||||
return getCgroupParamKeyValue(string(data))
|
||||
|
||||
return blkioStats, nil
|
||||
}
|
||||
|
||||
func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
||||
var blkioStats []cgroups.BlkioStatEntry
|
||||
var err error
|
||||
path, err := d.path("blkio")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.SectorsRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServiceBytesRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoServicedRecursive = blkioStats
|
||||
|
||||
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.BlkioStats.IoQueuedRecursive = blkioStats
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
switch t {
|
||||
case "nr_periods":
|
||||
stats.CpuStats.ThrottlingData.Periods = v
|
||||
|
||||
case "nr_throttled":
|
||||
stats.CpuStats.ThrottlingData.ThrottledPeriods = v
|
||||
|
||||
case "throttled_time":
|
||||
stats.CpuStats.ThrottlingData.ThrottledTime = v
|
||||
}
|
||||
paramData[t] = v
|
||||
}
|
||||
return paramData, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// TODO(vmarmol): This currently outputs nothing since the output is a string, fix.
|
||||
for _, param := range params {
|
||||
f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := strconv.ParseInt(strings.TrimSuffix(string(data), "\n"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramData[param] = v
|
||||
var data string
|
||||
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
|
||||
return err
|
||||
}
|
||||
return paramData, nil
|
||||
stats.FreezerStats.ParentState = data
|
||||
if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.FreezerStats.SelfState = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ package fs
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
|
||||
)
|
||||
|
||||
type memoryGroup struct {
|
||||
|
@ -50,17 +51,16 @@ func (s *memoryGroup) Remove(d *data) error {
|
|||
return removePath(d.path("memory"))
|
||||
}
|
||||
|
||||
func (s *memoryGroup) Stats(d *data) (map[string]int64, error) {
|
||||
paramData := make(map[string]int64)
|
||||
func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
||||
path, err := d.path("memory")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Set stats from memory.stat.
|
||||
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer statsFile.Close()
|
||||
|
||||
|
@ -68,23 +68,22 @@ func (s *memoryGroup) Stats(d *data) (map[string]int64, error) {
|
|||
for sc.Scan() {
|
||||
t, v, err := getCgroupParamKeyValue(sc.Text())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
paramData[t] = v
|
||||
stats.MemoryStats.Stats[t] = v
|
||||
}
|
||||
|
||||
// Set memory usage and max historical usage.
|
||||
params := []string{
|
||||
"usage_in_bytes",
|
||||
"max_usage_in_bytes",
|
||||
value, err := getCgroupParamInt(path, "memory.usage_in_bytes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, param := range params {
|
||||
value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramData[param] = value
|
||||
stats.MemoryStats.Usage = value
|
||||
value, err = getCgroupParamInt(path, "memory.max_usage_in_bytes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stats.MemoryStats.MaxUsage = value
|
||||
|
||||
return paramData, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 New Issue