Add systemd stats, reusing fs stats functionality
Docker-DCO-1.1-Signed-off-by: Jimmi Dyson <jimmidyson@gmail.com> (github: jimmidyson)
This commit is contained in:
parent
b3f798f9a7
commit
cbd37fba86
|
@ -12,21 +12,21 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
subsystems = map[string]subsystem{
|
subsystems = map[string]subsystem{
|
||||||
"devices": &devicesGroup{},
|
"devices": &DevicesGroup{},
|
||||||
"memory": &memoryGroup{},
|
"memory": &MemoryGroup{},
|
||||||
"cpu": &cpuGroup{},
|
"cpu": &CpuGroup{},
|
||||||
"cpuset": &cpusetGroup{},
|
"cpuset": &CpusetGroup{},
|
||||||
"cpuacct": &cpuacctGroup{},
|
"cpuacct": &CpuacctGroup{},
|
||||||
"blkio": &blkioGroup{},
|
"blkio": &BlkioGroup{},
|
||||||
"perf_event": &perfEventGroup{},
|
"perf_event": &PerfEventGroup{},
|
||||||
"freezer": &freezerGroup{},
|
"freezer": &FreezerGroup{},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type subsystem interface {
|
type subsystem interface {
|
||||||
Set(*data) error
|
Set(*data) error
|
||||||
Remove(*data) error
|
Remove(*data) error
|
||||||
GetStats(*data, *cgroups.Stats) error
|
GetStats(string, *cgroups.Stats) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type data struct {
|
type data struct {
|
||||||
|
@ -68,10 +68,18 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
|
||||||
return nil, fmt.Errorf("getting CgroupData %s", err)
|
return nil, fmt.Errorf("getting CgroupData %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for sysName, sys := range subsystems {
|
for sysname, sys := range subsystems {
|
||||||
// Don't fail if a cgroup hierarchy was not found.
|
path, err := d.path(sysname)
|
||||||
if err := sys.GetStats(d, stats); err != nil && err != cgroups.ErrNotFound {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting stats for system %q %s", sysName, err)
|
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
|
||||||
|
if err == cgroups.ErrNotFound {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sys.GetStats(path, stats); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type blkioGroup struct {
|
type BlkioGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blkioGroup) Set(d *data) error {
|
func (s *BlkioGroup) Set(d *data) error {
|
||||||
// we just want to join this group even though we don't set anything
|
// we just want to join this group even though we don't set anything
|
||||||
if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
|
if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
|
||||||
return err
|
return err
|
||||||
|
@ -22,7 +22,7 @@ func (s *blkioGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blkioGroup) Remove(d *data) error {
|
func (s *BlkioGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("blkio"))
|
return removePath(d.path("blkio"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,13 +113,9 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
|
||||||
return blkioStats, nil
|
return blkioStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
var blkioStats []cgroups.BlkioStatEntry
|
var blkioStats []cgroups.BlkioStatEntry
|
||||||
var err error
|
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 {
|
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -44,8 +44,8 @@ func TestBlkioStats(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,8 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) {
|
||||||
"blkio.io_queued_recursive": queuedRecursiveContents,
|
"blkio.io_queued_recursive": queuedRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed unexpectedly: %s", err)
|
t.Fatalf("Failed unexpectedly: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,8 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed unexpectedly: %s", err)
|
t.Fatalf("Failed unexpectedly: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -116,8 +116,8 @@ func TestBlkioStatsNoServicedFile(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed unexpectedly: %s", err)
|
t.Fatalf("Failed unexpectedly: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed unexpectedly: %s", err)
|
t.Fatalf("Failed unexpectedly: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -149,8 +149,8 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected to fail, but did not")
|
t.Fatal("Expected to fail, but did not")
|
||||||
}
|
}
|
||||||
|
@ -166,8 +166,8 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
|
||||||
"blkio.sectors_recursive": sectorsRecursiveContents,
|
"blkio.sectors_recursive": sectorsRecursiveContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
blkio := &blkioGroup{}
|
blkio := &BlkioGroup{}
|
||||||
err := blkio.GetStats(helper.CgroupData, &actualStats)
|
err := blkio.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected to fail, but did not")
|
t.Fatal("Expected to fail, but did not")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cpuGroup struct {
|
type CpuGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuGroup) Set(d *data) error {
|
func (s *CpuGroup) Set(d *data) error {
|
||||||
// We always want to join the cpu group, to allow fair cpu scheduling
|
// We always want to join the cpu group, to allow fair cpu scheduling
|
||||||
// on a container basis
|
// on a container basis
|
||||||
dir, err := d.join("cpu")
|
dir, err := d.join("cpu")
|
||||||
|
@ -37,16 +37,11 @@ func (s *cpuGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuGroup) Remove(d *data) error {
|
func (s *CpuGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("cpu"))
|
return removePath(d.path("cpu"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
path, err := d.path("cpu")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(filepath.Join(path, "cpu.stat"))
|
f, err := os.Open(filepath.Join(path, "cpu.stat"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
|
|
@ -23,8 +23,8 @@ func TestCpuStats(t *testing.T) {
|
||||||
"cpu.stat": cpuStatContent,
|
"cpu.stat": cpuStatContent,
|
||||||
})
|
})
|
||||||
|
|
||||||
cpu := &cpuGroup{}
|
cpu := &CpuGroup{}
|
||||||
err := cpu.GetStats(helper.CgroupData, &actualStats)
|
err := cpu.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,8 @@ func TestNoCpuStatFile(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("cpu", t)
|
helper := NewCgroupTestUtil("cpu", t)
|
||||||
defer helper.cleanup()
|
defer helper.cleanup()
|
||||||
|
|
||||||
cpu := &cpuGroup{}
|
cpu := &CpuGroup{}
|
||||||
err := cpu.GetStats(helper.CgroupData, &actualStats)
|
err := cpu.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected not to fail, but did")
|
t.Fatal("Expected not to fail, but did")
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ func TestInvalidCpuStat(t *testing.T) {
|
||||||
"cpu.stat": cpuStatContent,
|
"cpu.stat": cpuStatContent,
|
||||||
})
|
})
|
||||||
|
|
||||||
cpu := &cpuGroup{}
|
cpu := &CpuGroup{}
|
||||||
err := cpu.GetStats(helper.CgroupData, &actualStats)
|
err := cpu.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failed stat parsing.")
|
t.Fatal("Expected failed stat parsing.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,10 @@ var (
|
||||||
|
|
||||||
const nanosecondsInSecond = 1000000000
|
const nanosecondsInSecond = 1000000000
|
||||||
|
|
||||||
type cpuacctGroup struct {
|
type CpuacctGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuacctGroup) Set(d *data) error {
|
func (s *CpuacctGroup) Set(d *data) error {
|
||||||
// we just want to join this group even though we don't set anything
|
// we just want to join this group even though we don't set anything
|
||||||
if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
|
if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
|
||||||
return err
|
return err
|
||||||
|
@ -33,20 +33,20 @@ func (s *cpuacctGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuacctGroup) Remove(d *data) error {
|
func (s *CpuacctGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("cpuacct"))
|
return removePath(d.path("cpuacct"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
var (
|
var (
|
||||||
|
err error
|
||||||
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64
|
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64
|
||||||
)
|
)
|
||||||
path, err := d.path("cpuacct")
|
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
|
||||||
if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
startCpu = kernelModeUsage + userModeUsage
|
startCpu = kernelModeUsage + userModeUsage
|
||||||
if startSystem, err = s.getSystemCpuUsage(d); err != nil {
|
if startSystem, err = getSystemCpuUsage(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
startUsageTime := time.Now()
|
startUsageTime := time.Now()
|
||||||
|
@ -55,11 +55,11 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
||||||
}
|
}
|
||||||
// sample for 100ms
|
// sample for 100ms
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil {
|
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lastCpu = kernelModeUsage + userModeUsage
|
lastCpu = kernelModeUsage + userModeUsage
|
||||||
if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
|
if lastSystem, err = getSystemCpuUsage(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
usageSampleDuration := time.Since(startUsageTime)
|
usageSampleDuration := time.Since(startUsageTime)
|
||||||
|
@ -80,7 +80,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
||||||
stats.CpuStats.CpuUsage.PercentUsage = percentage
|
stats.CpuStats.CpuUsage.PercentUsage = percentage
|
||||||
// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
|
// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
|
||||||
stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
|
stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
|
||||||
percpuUsage, err := s.getPercpuUsage(path)
|
percpuUsage, err := getPercpuUsage(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(vmarmol): Use cgroups stats.
|
// TODO(vmarmol): Use cgroups stats.
|
||||||
func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
|
func getSystemCpuUsage() (uint64, error) {
|
||||||
|
|
||||||
f, err := os.Open("/proc/stat")
|
f, err := os.Open("/proc/stat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -125,7 +125,7 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
|
||||||
return 0, fmt.Errorf("invalid stat format")
|
return 0, fmt.Errorf("invalid stat format")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, uint64, error) {
|
func getCpuUsage(path string) (uint64, uint64, error) {
|
||||||
kernelModeUsage := uint64(0)
|
kernelModeUsage := uint64(0)
|
||||||
userModeUsage := uint64(0)
|
userModeUsage := uint64(0)
|
||||||
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.stat"))
|
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.stat"))
|
||||||
|
@ -146,7 +146,7 @@ func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, uint64, error)
|
||||||
return kernelModeUsage, userModeUsage, nil
|
return kernelModeUsage, userModeUsage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpuacctGroup) getPercpuUsage(path string) ([]uint64, error) {
|
func getPercpuUsage(path string) ([]uint64, error) {
|
||||||
percpuUsage := []uint64{}
|
percpuUsage := []uint64{}
|
||||||
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
|
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cpusetGroup struct {
|
type CpusetGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpusetGroup) Set(d *data) error {
|
func (s *CpusetGroup) Set(d *data) error {
|
||||||
// we don't want to join this cgroup unless it is specified
|
// we don't want to join this cgroup unless it is specified
|
||||||
if d.c.CpusetCpus != "" {
|
if d.c.CpusetCpus != "" {
|
||||||
dir, err := d.path("cpuset")
|
dir, err := d.path("cpuset")
|
||||||
|
@ -36,15 +36,15 @@ func (s *cpusetGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpusetGroup) Remove(d *data) error {
|
func (s *CpusetGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("cpuset"))
|
return removePath(d.path("cpuset"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
|
func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
|
||||||
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
|
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []b
|
||||||
// ensureParent ensures that the parent directory of current is created
|
// ensureParent ensures that the parent directory of current is created
|
||||||
// with the proper cpus and mems files copied from it's parent if the values
|
// with the proper cpus and mems files copied from it's parent if the values
|
||||||
// are a file with a new line char
|
// are a file with a new line char
|
||||||
func (s *cpusetGroup) ensureParent(current string) error {
|
func (s *CpusetGroup) ensureParent(current string) error {
|
||||||
parent := filepath.Dir(current)
|
parent := filepath.Dir(current)
|
||||||
|
|
||||||
if _, err := os.Stat(parent); err != nil {
|
if _, err := os.Stat(parent); err != nil {
|
||||||
|
@ -78,7 +78,7 @@ func (s *cpusetGroup) ensureParent(current string) error {
|
||||||
|
|
||||||
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
|
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
|
||||||
// directory to the current directory if the file's contents are 0
|
// directory to the current directory if the file's contents are 0
|
||||||
func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
|
func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
currentCpus, currentMems []byte
|
currentCpus, currentMems []byte
|
||||||
|
@ -105,6 +105,6 @@ func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cpusetGroup) isEmpty(b []byte) bool {
|
func (s *CpusetGroup) isEmpty(b []byte) bool {
|
||||||
return len(bytes.Trim(b, "\n")) == 0
|
return len(bytes.Trim(b, "\n")) == 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package fs
|
||||||
|
|
||||||
import "github.com/docker/libcontainer/cgroups"
|
import "github.com/docker/libcontainer/cgroups"
|
||||||
|
|
||||||
type devicesGroup struct {
|
type DevicesGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *devicesGroup) Set(d *data) error {
|
func (s *DevicesGroup) Set(d *data) error {
|
||||||
dir, err := d.join("devices")
|
dir, err := d.join("devices")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -25,10 +25,10 @@ func (s *devicesGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *devicesGroup) Remove(d *data) error {
|
func (s *DevicesGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("devices"))
|
return removePath(d.path("devices"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type freezerGroup struct {
|
type FreezerGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *freezerGroup) Set(d *data) error {
|
func (s *FreezerGroup) Set(d *data) error {
|
||||||
switch d.c.Freezer {
|
switch d.c.Freezer {
|
||||||
case cgroups.Frozen, cgroups.Thawed:
|
case cgroups.Frozen, cgroups.Thawed:
|
||||||
dir, err := d.path("freezer")
|
dir, err := d.path("freezer")
|
||||||
|
@ -43,7 +43,7 @@ func (s *freezerGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *freezerGroup) Remove(d *data) error {
|
func (s *FreezerGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("freezer"))
|
return removePath(d.path("freezer"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,12 +52,11 @@ func getFreezerFileData(path string) (string, error) {
|
||||||
return strings.TrimSuffix(string(data), "\n"), err
|
return strings.TrimSuffix(string(data), "\n"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
path, err := d.path("freezer")
|
var (
|
||||||
if err != nil {
|
data string
|
||||||
return err
|
err error
|
||||||
}
|
)
|
||||||
var data string
|
|
||||||
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
|
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memoryGroup struct {
|
type MemoryGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *memoryGroup) Set(d *data) error {
|
func (s *MemoryGroup) Set(d *data) error {
|
||||||
dir, err := d.join("memory")
|
dir, err := d.join("memory")
|
||||||
// only return an error for memory if it was not specified
|
// only return an error for memory if it was not specified
|
||||||
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
||||||
|
@ -47,16 +47,11 @@ func (s *memoryGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *memoryGroup) Remove(d *data) error {
|
func (s *MemoryGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("memory"))
|
return removePath(d.path("memory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
path, err := d.path("memory")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set stats from memory.stat.
|
// Set stats from memory.stat.
|
||||||
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
|
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -24,8 +24,8 @@ func TestMemoryStats(t *testing.T) {
|
||||||
"memory.failcnt": memoryFailcnt,
|
"memory.failcnt": memoryFailcnt,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,8 @@ func TestMemoryStatsNoStatFile(t *testing.T) {
|
||||||
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@ func TestMemoryStatsNoUsageFile(t *testing.T) {
|
||||||
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failure")
|
t.Fatal("Expected failure")
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
|
||||||
"memory.usage_in_bytes": memoryUsageContents,
|
"memory.usage_in_bytes": memoryUsageContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failure")
|
t.Fatal("Expected failure")
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,8 @@ func TestMemoryStatsBadStatFile(t *testing.T) {
|
||||||
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failure")
|
t.Fatal("Expected failure")
|
||||||
}
|
}
|
||||||
|
@ -103,8 +103,8 @@ func TestMemoryStatsBadUsageFile(t *testing.T) {
|
||||||
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
"memory.max_usage_in_bytes": memoryMaxUsageContents,
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failure")
|
t.Fatal("Expected failure")
|
||||||
}
|
}
|
||||||
|
@ -119,8 +119,8 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
|
||||||
"memory.max_usage_in_bytes": "bad",
|
"memory.max_usage_in_bytes": "bad",
|
||||||
})
|
})
|
||||||
|
|
||||||
memory := &memoryGroup{}
|
memory := &MemoryGroup{}
|
||||||
err := memory.GetStats(helper.CgroupData, &actualStats)
|
err := memory.GetStats(helper.CgroupPath, &actualStats)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected failure")
|
t.Fatal("Expected failure")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
type perfEventGroup struct {
|
type PerfEventGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *perfEventGroup) Set(d *data) error {
|
func (s *PerfEventGroup) Set(d *data) error {
|
||||||
// we just want to join this group even though we don't set anything
|
// we just want to join this group even though we don't set anything
|
||||||
if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
|
if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
|
||||||
return err
|
return err
|
||||||
|
@ -15,10 +15,10 @@ func (s *perfEventGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *perfEventGroup) Remove(d *data) error {
|
func (s *PerfEventGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("perf_event"))
|
return removePath(d.path("perf_event"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error {
|
func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,7 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
|
||||||
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
||||||
return fmt.Errorf("Systemd not supported")
|
return fmt.Errorf("Systemd not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
|
||||||
|
return nil, fmt.Errorf("Systemd not supported")
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
systemd1 "github.com/coreos/go-systemd/dbus"
|
systemd1 "github.com/coreos/go-systemd/dbus"
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/cgroups/fs"
|
||||||
"github.com/dotcloud/docker/pkg/systemd"
|
"github.com/dotcloud/docker/pkg/systemd"
|
||||||
"github.com/godbus/dbus"
|
"github.com/godbus/dbus"
|
||||||
)
|
)
|
||||||
|
@ -23,10 +24,24 @@ type systemdCgroup struct {
|
||||||
cleanupDirs []string
|
cleanupDirs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type subsystem interface {
|
||||||
|
GetStats(string, *cgroups.Stats) error
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
connLock sync.Mutex
|
connLock sync.Mutex
|
||||||
theConn *systemd1.Conn
|
theConn *systemd1.Conn
|
||||||
hasStartTransientUnit bool
|
hasStartTransientUnit bool
|
||||||
|
subsystems = map[string]subsystem{
|
||||||
|
"devices": &fs.DevicesGroup{},
|
||||||
|
"memory": &fs.MemoryGroup{},
|
||||||
|
"cpu": &fs.CpuGroup{},
|
||||||
|
"cpuset": &fs.CpusetGroup{},
|
||||||
|
"cpuacct": &fs.CpuacctGroup{},
|
||||||
|
"blkio": &fs.BlkioGroup{},
|
||||||
|
"perf_event": &fs.PerfEventGroup{},
|
||||||
|
"freezer": &fs.FreezerGroup{},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func UseSystemd() bool {
|
func UseSystemd() bool {
|
||||||
|
@ -316,7 +331,7 @@ func (c *systemdCgroup) Cleanup() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
|
func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
|
||||||
path, err := getFreezerPath(c)
|
path, err := getSubsystemPath(c, "freezer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -332,23 +347,27 @@ func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFreezerPath(c *cgroups.Cgroup) (string, error) {
|
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
|
||||||
mountpoint, err := cgroups.FindCgroupMountpoint("freezer")
|
mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
initPath, err := cgroups.GetInitCgroupDir("freezer")
|
initPath, err := cgroups.GetInitCgroupDir(subsystem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(mountpoint, initPath, fmt.Sprintf("%s-%s", c.Parent, c.Name)), nil
|
slice := "system.slice"
|
||||||
|
if c.Slice != "" {
|
||||||
|
slice = c.Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
||||||
path, err := getFreezerPath(c)
|
path, err := getSubsystemPath(c, "freezer")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -389,3 +408,28 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
|
||||||
func getUnitName(c *cgroups.Cgroup) string {
|
func getUnitName(c *cgroups.Cgroup) string {
|
||||||
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
|
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This would be nicer to get from the systemd API when accounting
|
||||||
|
* is enabled, but sadly there is no way to do that yet.
|
||||||
|
* The lack of this functionality in the API & the approach taken
|
||||||
|
* is guided by
|
||||||
|
* http://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#readingaccountinginformation.
|
||||||
|
*/
|
||||||
|
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
|
||||||
|
var subsystemPath string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
stats := cgroups.NewStats()
|
||||||
|
|
||||||
|
for sysname, sys := range subsystems {
|
||||||
|
if subsystemPath, err = getSubsystemPath(c, sysname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := sys.GetStats(subsystemPath, stats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue