cgroup/reader.go (141 lines of code) (raw):
package cgroup
import (
"path/filepath"
)
// Stats contains metrics and limits from each of the cgroup subsystems.
type Stats struct {
Metadata
CPU *CPUSubsystem `json:"cpu"`
CPUAccounting *CPUAccountingSubsystem `json:"cpuacct"`
Memory *MemorySubsystem `json:"memory"`
BlockIO *BlockIOSubsystem `json:"blkio"`
}
// Metadata contains metadata associated with cgroup stats.
type Metadata struct {
ID string `json:"id,omitempty"` // ID of the cgroup.
Path string `json:"path,omitempty"` // Path to the cgroup relative to the cgroup subsystem's mountpoint.
}
type mount struct {
subsystem string // Subsystem name (e.g. cpuacct).
mountpoint string // Mountpoint of the subsystem (e.g. /cgroup/cpuacct).
path string // Relative path to the cgroup (e.g. /docker/<id>).
id string // ID of the cgroup.
fullPath string // Absolute path to the cgroup. It's the mountpoint joined with the path.
}
// Reader reads cgroup metrics and limits.
type Reader struct {
// Mountpoint of the root filesystem. Defaults to / if not set. This can be
// useful for example if you mount / as /rootfs inside of a container.
rootfsMountpoint string
ignoreRootCgroups bool // Ignore a cgroup when its path is "/".
cgroupsHierarchyOverride string
cgroupMountpoints map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio).
}
// ReaderOptions holds options for NewReaderOptions.
type ReaderOptions struct {
// RootfsMountpoint holds the mountpoint of the root filesystem.
//
// If unspecified, "/" is assumed.
RootfsMountpoint string
// IgnoreRootCgroups ignores cgroup subsystem with the path "/".
IgnoreRootCgroups bool
// CgroupsHierarchyOverride is an optional path override for cgroup
// subsystem paths. If non-empty, this will be used instead of the
// paths specified in /proc/<pid>/cgroup.
//
// This should be set to "/" when running within a Docker container,
// where the paths in /proc/<pid>/cgroup do not correspond to any
// paths under /sys/fs/cgroup.
CgroupsHierarchyOverride string
}
// NewReader creates and returns a new Reader.
func NewReader(rootfsMountpoint string, ignoreRootCgroups bool) (*Reader, error) {
return NewReaderOptions(ReaderOptions{
RootfsMountpoint: rootfsMountpoint,
IgnoreRootCgroups: ignoreRootCgroups,
})
}
// NewReaderOptions creates and returns a new Reader with the given options.
func NewReaderOptions(opts ReaderOptions) (*Reader, error) {
if opts.RootfsMountpoint == "" {
opts.RootfsMountpoint = "/"
}
// Determine what subsystems are supported by the kernel.
subsystems, err := SupportedSubsystems(opts.RootfsMountpoint)
if err != nil {
return nil, err
}
// Locate the mountpoints of those subsystems.
mountpoints, err := SubsystemMountpoints(opts.RootfsMountpoint, subsystems)
if err != nil {
return nil, err
}
return &Reader{
rootfsMountpoint: opts.RootfsMountpoint,
ignoreRootCgroups: opts.IgnoreRootCgroups,
cgroupsHierarchyOverride: opts.CgroupsHierarchyOverride,
cgroupMountpoints: mountpoints,
}, nil
}
// GetStatsForProcess returns cgroup metrics and limits associated with a process.
func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) {
// Read /proc/[pid]/cgroup to get the paths to the cgroup metrics.
paths, err := ProcessCgroupPaths(r.rootfsMountpoint, pid)
if err != nil {
return nil, err
}
// Build the full path for the subsystems we are interested in.
mounts := map[string]mount{}
for _, interestedSubsystem := range []string{"blkio", "cpu", "cpuacct", "memory"} {
path, found := paths[interestedSubsystem]
if !found {
continue
}
if path == "/" && r.ignoreRootCgroups {
continue
}
subsystemMount, found := r.cgroupMountpoints[interestedSubsystem]
if !found {
continue
}
id := filepath.Base(path)
if r.cgroupsHierarchyOverride != "" {
path = r.cgroupsHierarchyOverride
}
mounts[interestedSubsystem] = mount{
subsystem: interestedSubsystem,
mountpoint: subsystemMount,
id: id,
path: path,
fullPath: filepath.Join(subsystemMount, path),
}
}
stats := Stats{Metadata: getCommonCgroupMetadata(mounts)}
// Collect stats from each cgroup subsystem associated with the task.
if mount, found := mounts["blkio"]; found {
stats.BlockIO = &BlockIOSubsystem{}
err := stats.BlockIO.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.BlockIO.Metadata.ID = mount.id
stats.BlockIO.Metadata.Path = mount.path
}
if mount, found := mounts["cpu"]; found {
stats.CPU = &CPUSubsystem{}
err := stats.CPU.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.CPU.Metadata.ID = mount.id
stats.CPU.Metadata.Path = mount.path
}
if mount, found := mounts["cpuacct"]; found {
stats.CPUAccounting = &CPUAccountingSubsystem{}
err := stats.CPUAccounting.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.CPUAccounting.Metadata.ID = mount.id
stats.CPUAccounting.Metadata.Path = mount.path
}
if mount, found := mounts["memory"]; found {
stats.Memory = &MemorySubsystem{}
err := stats.Memory.get(mount.fullPath)
if err != nil {
return nil, err
}
stats.Memory.Metadata.ID = mount.id
stats.Memory.Metadata.Path = mount.path
}
// Return nil if no metrics were collected.
if stats.BlockIO == nil && stats.CPU == nil && stats.CPUAccounting == nil && stats.Memory == nil {
return nil, nil
}
return &stats, nil
}
// getCommonCgroupMetadata returns Metadata containing the cgroup path and ID
// iff all subsystems share a common path and ID. This is common for
// containerized processes. If there is no common path and ID then the returned
// values are empty strings.
func getCommonCgroupMetadata(mounts map[string]mount) Metadata {
var path string
for _, m := range mounts {
if path == "" {
path = m.path
} else if path != m.path {
// All paths are not the same.
return Metadata{}
}
}
return Metadata{Path: path, ID: filepath.Base(path)}
}