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:
Jimmi Dyson 2014-06-20 14:13:56 +01:00 committed by Michael Crosby
parent b3f798f9a7
commit cbd37fba86
14 changed files with 158 additions and 117 deletions

View File

@ -12,21 +12,21 @@ import (
var (
subsystems = map[string]subsystem{
"devices": &devicesGroup{},
"memory": &memoryGroup{},
"cpu": &cpuGroup{},
"cpuset": &cpusetGroup{},
"cpuacct": &cpuacctGroup{},
"blkio": &blkioGroup{},
"perf_event": &perfEventGroup{},
"freezer": &freezerGroup{},
"devices": &DevicesGroup{},
"memory": &MemoryGroup{},
"cpu": &CpuGroup{},
"cpuset": &CpusetGroup{},
"cpuacct": &CpuacctGroup{},
"blkio": &BlkioGroup{},
"perf_event": &PerfEventGroup{},
"freezer": &FreezerGroup{},
}
)
type subsystem interface {
Set(*data) error
Remove(*data) error
GetStats(*data, *cgroups.Stats) error
GetStats(string, *cgroups.Stats) error
}
type data struct {
@ -68,10 +68,18 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
return nil, fmt.Errorf("getting CgroupData %s", err)
}
for sysName, sys := range subsystems {
// Don't fail if a cgroup hierarchy was not found.
if err := sys.GetStats(d, stats); err != nil && err != cgroups.ErrNotFound {
return nil, fmt.Errorf("getting stats for system %q %s", sysName, err)
for sysname, sys := range subsystems {
path, err := d.path(sysname)
if err != nil {
// 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
}
}

View File

@ -11,10 +11,10 @@ import (
"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
if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
return err
@ -22,7 +22,7 @@ func (s *blkioGroup) Set(d *data) error {
return nil
}
func (s *blkioGroup) Remove(d *data) error {
func (s *BlkioGroup) Remove(d *data) error {
return removePath(d.path("blkio"))
}
@ -113,13 +113,9 @@ func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
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 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

View File

@ -44,8 +44,8 @@ func TestBlkioStats(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -84,8 +84,8 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) {
"blkio.io_queued_recursive": queuedRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
@ -100,8 +100,8 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
@ -116,8 +116,8 @@ func TestBlkioStatsNoServicedFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
@ -132,8 +132,8 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatalf("Failed unexpectedly: %s", err)
}
@ -149,8 +149,8 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
}
@ -166,8 +166,8 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
"blkio.sectors_recursive": sectorsRecursiveContents,
})
blkio := &blkioGroup{}
err := blkio.GetStats(helper.CgroupData, &actualStats)
blkio := &BlkioGroup{}
err := blkio.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected to fail, but did not")
}

View File

@ -9,10 +9,10 @@ import (
"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
// on a container basis
dir, err := d.join("cpu")
@ -37,16 +37,11 @@ func (s *cpuGroup) Set(d *data) error {
return nil
}
func (s *cpuGroup) Remove(d *data) error {
func (s *CpuGroup) Remove(d *data) error {
return removePath(d.path("cpu"))
}
func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("cpu")
if err != nil {
return err
}
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
f, err := os.Open(filepath.Join(path, "cpu.stat"))
if err != nil {
if os.IsNotExist(err) {

View File

@ -23,8 +23,8 @@ func TestCpuStats(t *testing.T) {
"cpu.stat": cpuStatContent,
})
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -41,8 +41,8 @@ func TestNoCpuStatFile(t *testing.T) {
helper := NewCgroupTestUtil("cpu", t)
defer helper.cleanup()
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal("Expected not to fail, but did")
}
@ -58,8 +58,8 @@ func TestInvalidCpuStat(t *testing.T) {
"cpu.stat": cpuStatContent,
})
cpu := &cpuGroup{}
err := cpu.GetStats(helper.CgroupData, &actualStats)
cpu := &CpuGroup{}
err := cpu.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failed stat parsing.")
}

View File

@ -22,10 +22,10 @@ var (
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
if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
return err
@ -33,20 +33,20 @@ func (s *cpuacctGroup) Set(d *data) error {
return nil
}
func (s *cpuacctGroup) Remove(d *data) error {
func (s *CpuacctGroup) Remove(d *data) error {
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 (
err error
startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64
)
path, err := d.path("cpuacct")
if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil {
if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil {
return err
}
startCpu = kernelModeUsage + userModeUsage
if startSystem, err = s.getSystemCpuUsage(d); err != nil {
if startSystem, err = getSystemCpuUsage(); err != nil {
return err
}
startUsageTime := time.Now()
@ -55,11 +55,11 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
}
// sample for 100ms
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
}
lastCpu = kernelModeUsage + userModeUsage
if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
if lastSystem, err = getSystemCpuUsage(); err != nil {
return err
}
usageSampleDuration := time.Since(startUsageTime)
@ -80,7 +80,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
stats.CpuStats.CpuUsage.PercentUsage = percentage
// 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())
percpuUsage, err := s.getPercpuUsage(path)
percpuUsage, err := getPercpuUsage(path)
if err != nil {
return err
}
@ -92,7 +92,7 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
}
// TODO(vmarmol): Use cgroups stats.
func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
func getSystemCpuUsage() (uint64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
@ -125,7 +125,7 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
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)
userModeUsage := uint64(0)
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
}
func (s *cpuacctGroup) getPercpuUsage(path string) ([]uint64, error) {
func getPercpuUsage(path string) ([]uint64, error) {
percpuUsage := []uint64{}
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
if err != nil {

View File

@ -10,10 +10,10 @@ import (
"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
if d.c.CpusetCpus != "" {
dir, err := d.path("cpuset")
@ -36,15 +36,15 @@ func (s *cpusetGroup) Set(d *data) error {
return nil
}
func (s *cpusetGroup) Remove(d *data) error {
func (s *CpusetGroup) Remove(d *data) error {
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
}
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 {
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
// with the proper cpus and mems files copied from it's parent if the values
// 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)
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
// 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 (
err error
currentCpus, currentMems []byte
@ -105,6 +105,6 @@ func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
return nil
}
func (s *cpusetGroup) isEmpty(b []byte) bool {
func (s *CpusetGroup) isEmpty(b []byte) bool {
return len(bytes.Trim(b, "\n")) == 0
}

View File

@ -2,10 +2,10 @@ package fs
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")
if err != nil {
return err
@ -25,10 +25,10 @@ func (s *devicesGroup) Set(d *data) error {
return nil
}
func (s *devicesGroup) Remove(d *data) error {
func (s *DevicesGroup) Remove(d *data) error {
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
}

View File

@ -9,10 +9,10 @@ import (
"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 {
case cgroups.Frozen, cgroups.Thawed:
dir, err := d.path("freezer")
@ -43,7 +43,7 @@ func (s *freezerGroup) Set(d *data) error {
return nil
}
func (s *freezerGroup) Remove(d *data) error {
func (s *FreezerGroup) Remove(d *data) error {
return removePath(d.path("freezer"))
}
@ -52,12 +52,11 @@ func getFreezerFileData(path string) (string, error) {
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 err
}
var data string
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
var (
data string
err error
)
if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
return err
}

View File

@ -9,10 +9,10 @@ import (
"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")
// 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) {
@ -47,16 +47,11 @@ func (s *memoryGroup) Set(d *data) error {
return nil
}
func (s *memoryGroup) Remove(d *data) error {
func (s *MemoryGroup) Remove(d *data) error {
return removePath(d.path("memory"))
}
func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
path, err := d.path("memory")
if err != nil {
return err
}
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
// Set stats from memory.stat.
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
if err != nil {

View File

@ -24,8 +24,8 @@ func TestMemoryStats(t *testing.T) {
"memory.failcnt": memoryFailcnt,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -41,8 +41,8 @@ func TestMemoryStatsNoStatFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err != nil {
t.Fatal(err)
}
@ -56,8 +56,8 @@ func TestMemoryStatsNoUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -71,8 +71,8 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
"memory.usage_in_bytes": memoryUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -87,8 +87,8 @@ func TestMemoryStatsBadStatFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -103,8 +103,8 @@ func TestMemoryStatsBadUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": memoryMaxUsageContents,
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}
@ -119,8 +119,8 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
"memory.max_usage_in_bytes": "bad",
})
memory := &memoryGroup{}
err := memory.GetStats(helper.CgroupData, &actualStats)
memory := &MemoryGroup{}
err := memory.GetStats(helper.CgroupPath, &actualStats)
if err == nil {
t.Fatal("Expected failure")
}

View File

@ -4,10 +4,10 @@ import (
"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
if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
return err
@ -15,10 +15,10 @@ func (s *perfEventGroup) Set(d *data) error {
return nil
}
func (s *perfEventGroup) Remove(d *data) error {
func (s *PerfEventGroup) Remove(d *data) error {
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
}

View File

@ -23,3 +23,7 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
return fmt.Errorf("Systemd not supported")
}
func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
return nil, fmt.Errorf("Systemd not supported")
}

View File

@ -15,6 +15,7 @@ import (
systemd1 "github.com/coreos/go-systemd/dbus"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/dotcloud/docker/pkg/systemd"
"github.com/godbus/dbus"
)
@ -23,10 +24,24 @@ type systemdCgroup struct {
cleanupDirs []string
}
type subsystem interface {
GetStats(string, *cgroups.Stats) error
}
var (
connLock sync.Mutex
theConn *systemd1.Conn
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 {
@ -316,7 +331,7 @@ func (c *systemdCgroup) Cleanup() error {
}
func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
path, err := getFreezerPath(c)
path, err := getSubsystemPath(c, "freezer")
if err != nil {
return "", err
}
@ -332,23 +347,27 @@ func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
return path, nil
}
func getFreezerPath(c *cgroups.Cgroup) (string, error) {
mountpoint, err := cgroups.FindCgroupMountpoint("freezer")
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
if err != nil {
return "", err
}
initPath, err := cgroups.GetInitCgroupDir("freezer")
initPath, err := cgroups.GetInitCgroupDir(subsystem)
if err != nil {
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 {
path, err := getFreezerPath(c)
path, err := getSubsystemPath(c, "freezer")
if err != nil {
return err
}
@ -389,3 +408,28 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) {
func getUnitName(c *cgroups.Cgroup) string {
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
}