diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e9461c1..5500dca75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add method of overriding the /sys/fs/cgroup hierarchy, for reading cgroup metrics inside Docker [#148](https://github.com/elastic/gosigar/pull/148) + ### Fixed ### Changed diff --git a/cgroup/reader.go b/cgroup/reader.go index 894061d12..ebb2d7275 100644 --- a/cgroup/reader.go +++ b/cgroup/reader.go @@ -31,33 +31,63 @@ type mount struct { 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 "/". - cgroupMountpoints map[string]string // Mountpoints for each subsystem (e.g. cpu, cpuacct, memory, blkio). + 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//cgroup. + // + // This should be set to "/" when running within a Docker container, + // where the paths in /proc//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) { - if rootfsMountpoint == "" { - rootfsMountpoint = "/" + 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(rootfsMountpoint) + subsystems, err := SupportedSubsystems(opts.RootfsMountpoint) if err != nil { return nil, err } // Locate the mountpoints of those subsystems. - mountpoints, err := SubsystemMountpoints(rootfsMountpoint, subsystems) + mountpoints, err := SubsystemMountpoints(opts.RootfsMountpoint, subsystems) if err != nil { return nil, err } return &Reader{ - rootfsMountpoint: rootfsMountpoint, - ignoreRootCgroups: ignoreRootCgroups, - cgroupMountpoints: mountpoints, + rootfsMountpoint: opts.RootfsMountpoint, + ignoreRootCgroups: opts.IgnoreRootCgroups, + cgroupsHierarchyOverride: opts.CgroupsHierarchyOverride, + cgroupMountpoints: mountpoints, }, nil } @@ -86,11 +116,15 @@ func (r *Reader) GetStatsForProcess(pid int) (*Stats, error) { continue } + id := filepath.Base(path) + if r.cgroupsHierarchyOverride != "" { + path = r.cgroupsHierarchyOverride + } mounts[interestedSubsystem] = mount{ subsystem: interestedSubsystem, mountpoint: subsystemMount, + id: id, path: path, - id: filepath.Base(path), fullPath: filepath.Join(subsystemMount, path), } } diff --git a/cgroup/reader_test.go b/cgroup/reader_test.go index c8e702581..0a8b9a09e 100644 --- a/cgroup/reader_test.go +++ b/cgroup/reader_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -45,3 +46,33 @@ func TestReaderGetStats(t *testing.T) { t.Log(string(json)) } + +func TestReaderGetStatsHierarchyOverride(t *testing.T) { + // In testdata/docker, process 1's cgroup paths have + // no corresponding paths under /sys/fs/cgroup/. + // + // Setting CgroupsHierarchyOverride means that we use + // the root cgroup path instead. This is intended to test + // the scenario where we're reading cgroup metrics from + // within a Docker container. + + reader, err := NewReaderOptions(ReaderOptions{ + RootfsMountpoint: "testdata/docker", + IgnoreRootCgroups: true, + CgroupsHierarchyOverride: "/", + }) + if err != nil { + t.Fatal(err) + } + + stats, err := reader.GetStatsForProcess(1) + if err != nil { + t.Fatal(err) + } + if stats == nil { + t.Fatal("no cgroup stats found") + } + + require.NotNil(t, stats.CPU) + assert.NotZero(t, stats.CPU.CFS.Shares) +} diff --git a/cgroup/testdata/docker/proc/1/cgroup b/cgroup/testdata/docker/proc/1/cgroup new file mode 100644 index 000000000..591542917 --- /dev/null +++ b/cgroup/testdata/docker/proc/1/cgroup @@ -0,0 +1,10 @@ +10:net_prio:/docker/123 +9:perf_event:/docker/123 +8:net_cls:/docker/123 +7:freezer:/docker/123 +6:devices:/docker/123 +5:memory:/docker/123 +4:blkio:/docker/123 +3:cpuacct:/docker/123 +2:cpu:/docker/123 +1:cpuset:/docker/123