Skip to content

Commit

Permalink
support cgroup v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Curl-Li committed Sep 10, 2022
1 parent 9588228 commit b989991
Show file tree
Hide file tree
Showing 52 changed files with 1,699 additions and 50 deletions.
6 changes: 0 additions & 6 deletions consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ var check2name = map[configureType]string{
gcHeap: "GCHeap",
}

const (
cgroupMemLimitPath = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
cgroupCpuQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
cgroupCpuPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
)

const minCollectCyclesBeforeDumpStart = 10

const (
Expand Down
2 changes: 1 addition & 1 deletion doc/zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ h, _ := holmes.New(
```

### 在docker 或者cgroup环境下运行 holmes

支持 Cgroup V1, V2 两个版本, 但暂不支持混用。
```go
h, _ := holmes.New(
holmes.WithCollectInterval("5s"),
Expand Down
74 changes: 74 additions & 0 deletions internal/cg/cgroups/cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build linux
// +build linux

package cgroups

import (
"bufio"
"io"
"os"
"path/filepath"
"strconv"
)

// CGroup represents the data structure for a Linux control group.
type CGroup struct {
path string
}

// NewCGroup returns a new *CGroup from a given path.
func NewCGroup(path string) CGroup {
return CGroup{path: path}
}

// Path returns the path of the CGroup*.
func (cg *CGroup) Path() string {
return cg.path
}

// ParamPath returns the path of the given cgroup param under itself.
func (cg *CGroup) ParamPath(param string) string {
return filepath.Join(cg.path, param)
}

// readFirstLine reads the first line from a cgroup param file.
func (cg *CGroup) readFirstLine(param string) (string, error) {
paramFile, err := os.Open(cg.ParamPath(param))
if err != nil {
return "", err
}
defer paramFile.Close() // nolint: errcheck

scanner := bufio.NewScanner(paramFile)
if scanner.Scan() {
return scanner.Text(), nil
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", io.ErrUnexpectedEOF
}

// readInt parses the first line from a cgroup param file as int.
func (cg *CGroup) readInt(param string) (int, error) {
text, err := cg.readFirstLine(param)
if err != nil {
return 0, err
}
return strconv.Atoi(text)
}
122 changes: 122 additions & 0 deletions internal/cg/cgroups/cgroup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build linux
// +build linux

package cgroups

import (
"path/filepath"
"testing"

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

func TestCGroupParamPath(t *testing.T) {
cgroup := NewCGroup("/sys/fs/cgroup/cpu")
assert.Equal(t, "/sys/fs/cgroup/cpu", cgroup.Path())
assert.Equal(t, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", cgroup.ParamPath("cpu.cfs_quota_us"))
}

func TestCGroupReadFirstLine(t *testing.T) {
testTable := []struct {
name string
paramName string
expectedContent string
shouldHaveError bool
}{
{
name: "set",
paramName: "cpu.cfs_period_us",
expectedContent: "100000",
shouldHaveError: false,
},
{
name: "absent",
paramName: "cpu.stat",
expectedContent: "",
shouldHaveError: true,
},
{
name: "empty",
paramName: "cpu.cfs_quota_us",
expectedContent: "",
shouldHaveError: true,
},
}

for _, tt := range testTable {
cgroupPath := filepath.Join(testDataCGroupsPath, "v1", "cpu", tt.name)
cgroup := NewCGroup(cgroupPath)

content, err := cgroup.readFirstLine(tt.paramName)
assert.Equal(t, tt.expectedContent, content, tt.name)

if tt.shouldHaveError {
assert.Error(t, err, tt.name)
} else {
assert.NoError(t, err, tt.name)
}
}
}

func TestCGroupReadInt(t *testing.T) {
testTable := []struct {
name string
paramName string
expectedValue int
shouldHaveError bool
}{
{
name: "set",
paramName: "cpu.cfs_period_us",
expectedValue: 100000,
shouldHaveError: false,
},
{
name: "empty",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
{
name: "invalid",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
{
name: "absent",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
}

for _, tt := range testTable {
cgroupPath := filepath.Join(testDataCGroupsPath, "v1", "cpu", tt.name)
cgroup := NewCGroup(cgroupPath)

value, err := cgroup.readInt(tt.paramName)
assert.Equal(t, tt.expectedValue, value, "%s/%s", tt.name, tt.paramName)

if tt.shouldHaveError {
assert.Error(t, err, tt.name)
} else {
assert.NoError(t, err, tt.name)
}
}
}
109 changes: 109 additions & 0 deletions internal/cg/cgroups/cgroups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build linux
// +build linux

package cgroups

const (
// _cgroupFSType is the Linux CGroup file system type used in
// `/proc/$PID/mountinfo`.
_cgroupFSType = "cgroup"
// _cgroupSubsysCPU is the CPU CGroup subsystem.
_cgroupSubsysCPU = "cpu"
// _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem.
_cgroupSubsysCPUAcct = "cpuacct" // nolint:unused
// _cgroupSubsysCPUSet is the CPUSet CGroup subsystem.
_cgroupSubsysCPUSet = "cpuset" // nolint:unused
// _cgroupSubsysMemory is the Memory CGroup subsystem.
_cgroupSubsysMemory = "memory"

// _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota
// parameter.
_cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us"
// _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period
// parameter.
_cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us"
// _cgroupMemLimitParam is the file name for the CGroup CFS memory
// parameter.
_cgroupMemLimitParam = "memory.limit_in_bytes"
)

// CGroups is a map that associates each CGroup with its subsystem name.
type CGroups map[string]CGroup

func newCGroups(mountInfo []*MountPoint, subsystems map[string]*CGroupSubsys) (CGroups, error) {
cgroups := make(CGroups)
for _, mp := range mountInfo {
if mp.FSType != _cgroupFSType {
continue
}
for _, opt := range mp.SuperOptions {
subsys, exists := subsystems[opt]
if !exists {
continue
}

cgroupPath, err := mp.Translate(subsys.Name)
if err != nil {
return nil, err
}
cgroups[opt] = NewCGroup(cgroupPath)
}

}
return cgroups, nil
}

// MemLimit returns the memory limit with memory cgroup controller.
// `memory.max` wat not set, the method returns `(-1, nil)`
func (cg CGroups) MemLimit() (int, bool, error) {
memCGroup, ok := cg[_cgroupSubsysMemory]
if !ok {
return -1, false, nil
}
memLimit, err := memCGroup.readInt(_cgroupMemLimitParam)
if defined := memLimit > 0; err != nil || !defined {
return -1, defined, err
}
return memLimit, true, nil
}

// CPUQuota returns the CPU quota applied with the CPU cgroup controller.
// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of
// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`.
func (cg CGroups) CPUQuota() (float64, bool, error) {
cpuCGroup, exists := cg[_cgroupSubsysCPU]
if !exists {
return -1, false, nil
}

cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
if defined := cfsQuotaUs > 0; err != nil || !defined {
return -1, defined, err
}

cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)
if err != nil {
return -1, false, err
}

return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
}

func (cg CGroups) Version() string {
return _cgroupFSType
}
Loading

0 comments on commit b989991

Please sign in to comment.