Skip to content

Commit e06c605

Browse files
committed
support cgroup v2
1 parent 9588228 commit e06c605

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1702
-50
lines changed

consts.go

-6
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,6 @@ var check2name = map[configureType]string{
9292
gcHeap: "GCHeap",
9393
}
9494

95-
const (
96-
cgroupMemLimitPath = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
97-
cgroupCpuQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
98-
cgroupCpuPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
99-
)
100-
10195
const minCollectCyclesBeforeDumpStart = 10
10296

10397
const (

doc/zh.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ h, _ := holmes.New(
235235
```
236236

237237
### 在docker 或者cgroup环境下运行 holmes
238-
238+
支持 Cgroup V1, V2 两个版本, 但暂不支持混用。
239239
```go
240240
h, _ := holmes.New(
241241
holmes.WithCollectInterval("5s"),

internal/cg/cgroups/cgroup.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//go:build linux
17+
// +build linux
18+
19+
package cgroups
20+
21+
import (
22+
"bufio"
23+
"io"
24+
"os"
25+
"path/filepath"
26+
"strconv"
27+
)
28+
29+
// CGroup represents the data structure for a Linux control group.
30+
type CGroup struct {
31+
path string
32+
}
33+
34+
// NewCGroup returns a new *CGroup from a given path.
35+
func NewCGroup(path string) CGroup {
36+
return CGroup{path: path}
37+
}
38+
39+
// Path returns the path of the CGroup*.
40+
func (cg *CGroup) Path() string {
41+
return cg.path
42+
}
43+
44+
// ParamPath returns the path of the given cgroup param under itself.
45+
func (cg *CGroup) ParamPath(param string) string {
46+
return filepath.Join(cg.path, param)
47+
}
48+
49+
// readFirstLine reads the first line from a cgroup param file.
50+
func (cg *CGroup) readFirstLine(param string) (string, error) {
51+
paramFile, err := os.Open(cg.ParamPath(param))
52+
if err != nil {
53+
return "", err
54+
}
55+
defer paramFile.Close() // nolint: errcheck
56+
57+
scanner := bufio.NewScanner(paramFile)
58+
if scanner.Scan() {
59+
return scanner.Text(), nil
60+
}
61+
if err := scanner.Err(); err != nil {
62+
return "", err
63+
}
64+
return "", io.ErrUnexpectedEOF
65+
}
66+
67+
// readInt parses the first line from a cgroup param file as int.
68+
func (cg *CGroup) readInt(param string) (int, error) {
69+
text, err := cg.readFirstLine(param)
70+
if err != nil {
71+
return 0, err
72+
}
73+
return strconv.Atoi(text)
74+
}

internal/cg/cgroups/cgroup_test.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//go:build linux
17+
// +build linux
18+
19+
package cgroups
20+
21+
import (
22+
"path/filepath"
23+
"testing"
24+
25+
"github.com/stretchr/testify/assert"
26+
)
27+
28+
func TestCGroupParamPath(t *testing.T) {
29+
cgroup := NewCGroup("/sys/fs/cgroup/cpu")
30+
assert.Equal(t, "/sys/fs/cgroup/cpu", cgroup.Path())
31+
assert.Equal(t, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", cgroup.ParamPath("cpu.cfs_quota_us"))
32+
}
33+
34+
func TestCGroupReadFirstLine(t *testing.T) {
35+
testTable := []struct {
36+
name string
37+
paramName string
38+
expectedContent string
39+
shouldHaveError bool
40+
}{
41+
{
42+
name: "set",
43+
paramName: "cpu.cfs_period_us",
44+
expectedContent: "100000",
45+
shouldHaveError: false,
46+
},
47+
{
48+
name: "absent",
49+
paramName: "cpu.stat",
50+
expectedContent: "",
51+
shouldHaveError: true,
52+
},
53+
{
54+
name: "empty",
55+
paramName: "cpu.cfs_quota_us",
56+
expectedContent: "",
57+
shouldHaveError: true,
58+
},
59+
}
60+
61+
for _, tt := range testTable {
62+
cgroupPath := filepath.Join(testDataCGroupsPath, "v1", "cpu", tt.name)
63+
cgroup := NewCGroup(cgroupPath)
64+
65+
content, err := cgroup.readFirstLine(tt.paramName)
66+
assert.Equal(t, tt.expectedContent, content, tt.name)
67+
68+
if tt.shouldHaveError {
69+
assert.Error(t, err, tt.name)
70+
} else {
71+
assert.NoError(t, err, tt.name)
72+
}
73+
}
74+
}
75+
76+
func TestCGroupReadInt(t *testing.T) {
77+
testTable := []struct {
78+
name string
79+
paramName string
80+
expectedValue int
81+
shouldHaveError bool
82+
}{
83+
{
84+
name: "set",
85+
paramName: "cpu.cfs_period_us",
86+
expectedValue: 100000,
87+
shouldHaveError: false,
88+
},
89+
{
90+
name: "empty",
91+
paramName: "cpu.cfs_quota_us",
92+
expectedValue: 0,
93+
shouldHaveError: true,
94+
},
95+
{
96+
name: "invalid",
97+
paramName: "cpu.cfs_quota_us",
98+
expectedValue: 0,
99+
shouldHaveError: true,
100+
},
101+
{
102+
name: "absent",
103+
paramName: "cpu.cfs_quota_us",
104+
expectedValue: 0,
105+
shouldHaveError: true,
106+
},
107+
}
108+
109+
for _, tt := range testTable {
110+
cgroupPath := filepath.Join(testDataCGroupsPath, "v1", "cpu", tt.name)
111+
cgroup := NewCGroup(cgroupPath)
112+
113+
value, err := cgroup.readInt(tt.paramName)
114+
assert.Equal(t, tt.expectedValue, value, "%s/%s", tt.name, tt.paramName)
115+
116+
if tt.shouldHaveError {
117+
assert.Error(t, err, tt.name)
118+
} else {
119+
assert.NoError(t, err, tt.name)
120+
}
121+
}
122+
}

internal/cg/cgroups/cgroups.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
//go:build linux
17+
// +build linux
18+
19+
package cgroups
20+
21+
const (
22+
// _cgroupFSType is the Linux CGroup file system type used in
23+
// `/proc/$PID/mountinfo`.
24+
_cgroupFSType = "cgroup"
25+
// _cgroupSubsysCPU is the CPU CGroup subsystem.
26+
_cgroupSubsysCPU = "cpu"
27+
// _cgroupSubsysMemory is the Memory CGroup subsystem.
28+
_cgroupSubsysMemory = "memory"
29+
30+
// _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota
31+
// parameter.
32+
_cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us"
33+
// _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period
34+
// parameter.
35+
_cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us"
36+
// _cgroupMemLimitParam is the file name for the CGroup CFS memory
37+
// parameter.
38+
_cgroupMemLimitParam = "memory.limit_in_bytes"
39+
)
40+
41+
// CGroups is a map that associates each CGroup with its subsystem name.
42+
type CGroups map[string]CGroup
43+
44+
func newCGroups(mountInfo []*MountPoint, subsystems map[string]*CGroupSubsys) (CGroups, error) {
45+
cgroups := make(CGroups)
46+
for _, mp := range mountInfo {
47+
if mp.FSType != _cgroupFSType {
48+
continue
49+
}
50+
for _, opt := range mp.SuperOptions {
51+
subsys, exists := subsystems[opt]
52+
if !exists {
53+
continue
54+
}
55+
56+
cgroupPath, err := mp.Translate(subsys.Name)
57+
if err != nil {
58+
return nil, err
59+
}
60+
cgroups[opt] = NewCGroup(cgroupPath)
61+
}
62+
63+
}
64+
return cgroups, nil
65+
}
66+
67+
// MemLimit returns the memory limit with memory cgroup controller.
68+
// `memory.max` wat not set, the method returns `(-1, nil)`
69+
func (cg CGroups) MemLimit() (int, bool, error) {
70+
memCGroup, ok := cg[_cgroupSubsysMemory]
71+
if !ok {
72+
return -1, false, nil
73+
}
74+
memLimit, err := memCGroup.readInt(_cgroupMemLimitParam)
75+
if defined := memLimit > 0; err != nil || !defined {
76+
return -1, defined, err
77+
}
78+
return memLimit, true, nil
79+
}
80+
81+
// CPUQuota returns the CPU quota applied with the CPU cgroup controller.
82+
// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of
83+
// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`.
84+
func (cg CGroups) CPUQuota() (float64, bool, error) {
85+
cpuCGroup, exists := cg[_cgroupSubsysCPU]
86+
if !exists {
87+
return -1, false, nil
88+
}
89+
90+
cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
91+
if defined := cfsQuotaUs > 0; err != nil || !defined {
92+
return -1, defined, err
93+
}
94+
95+
cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)
96+
if err != nil {
97+
return -1, false, err
98+
}
99+
100+
return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
101+
}
102+
103+
func (cg CGroups) Version() string {
104+
return _cgroupFSType
105+
}

0 commit comments

Comments
 (0)