Skip to content

Commit

Permalink
cgroup: add option to override the /sys/fs/cgroup hierarchy (#148)
Browse files Browse the repository at this point in the history
* cgroup: add option to override the /sys/fs/cgroup hierarchy

When running in Docker, paths in /proc/<pid>/cgroup do not
correspond to the paths under /sys/fs/cgroup/<subsystem>/...

Instead, the root (/sys/fs/cgroup/<subsystem/>) should be
used there. Provide an option for overridding the path.
  • Loading branch information
axw authored Dec 3, 2020
1 parent 226a389 commit f92177c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 45 additions & 11 deletions cgroup/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/<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) {
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
}

Expand Down Expand Up @@ -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),
}
}
Expand Down
31 changes: 31 additions & 0 deletions cgroup/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
Expand Down Expand Up @@ -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/<subsystem>.
//
// 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)
}
10 changes: 10 additions & 0 deletions cgroup/testdata/docker/proc/1/cgroup
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit f92177c

Please sign in to comment.