Skip to content

Commit cb844f1

Browse files
Furistoroboquat
authored andcommitted
[common] Introduce cgroup library
1 parent 9467431 commit cb844f1

File tree

7 files changed

+401
-0
lines changed

7 files changed

+401
-0
lines changed

components/common-go/cgroups/cgroup.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
package cgroups
66

77
import (
8+
"math"
9+
"os"
10+
"strconv"
11+
"strings"
12+
813
"github.com/containerd/cgroups"
914
v2 "github.com/containerd/cgroups/v2"
1015
)
1116

17+
const DefaultMountPoint = "/sys/fs/cgroup"
18+
1219
func IsUnifiedCgroupSetup() (bool, error) {
1320
return cgroups.Mode() == cgroups.Unified, nil
1421
}
@@ -26,3 +33,55 @@ func EnsureCpuControllerEnabled(basePath, cgroupPath string) error {
2633

2734
return nil
2835
}
36+
37+
type CpuStats struct {
38+
UsageTotal uint64
39+
UsageUser uint64
40+
UsageSystem uint64
41+
}
42+
43+
type MemoryStats struct {
44+
InactiveFileTotal uint64
45+
}
46+
47+
func ReadSingleValue(path string) (uint64, error) {
48+
content, err := os.ReadFile(path)
49+
if err != nil {
50+
return 0, err
51+
}
52+
53+
value := strings.TrimSpace(string(content))
54+
if value == "max" || value == "-1" {
55+
return math.MaxUint64, nil
56+
}
57+
58+
max, err := strconv.ParseUint(value, 10, 64)
59+
if err != nil {
60+
return 0, err
61+
}
62+
63+
return max, nil
64+
}
65+
66+
func ReadFlatKeyedFile(path string) (map[string]uint64, error) {
67+
content, err := os.ReadFile(path)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
entries := strings.Split(strings.TrimSpace(string(content)), "\n")
73+
kv := make(map[string]uint64, len(entries))
74+
for _, entry := range entries {
75+
tokens := strings.Split(entry, " ")
76+
if len(tokens) < 2 {
77+
continue
78+
}
79+
v, err := strconv.ParseUint(tokens[1], 10, 64)
80+
if err != nil {
81+
continue
82+
}
83+
kv[tokens[0]] = v
84+
}
85+
86+
return kv, nil
87+
}

components/common-go/cgroups/cgroups_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
package cgroups
66

77
import (
8+
"math"
89
"os"
910
"path/filepath"
1011
"testing"
12+
13+
"github.com/stretchr/testify/assert"
1114
)
1215

1316
var cgroupPath = []string{"/kubepods", "burstable", "pods234sdf", "234as8df34"}
@@ -87,3 +90,47 @@ func verifyCpuControllerToggled(t *testing.T, path string, enabled bool) {
8790
t.Fatalf("%s should not have enabled cpu controller", path)
8891
}
8992
}
93+
94+
func TestReadSingleValue(t *testing.T) {
95+
scenarios := []struct {
96+
name string
97+
content string
98+
expected uint64
99+
}{
100+
{
101+
name: "cgroup2 max value",
102+
content: "max",
103+
expected: math.MaxUint64,
104+
},
105+
{
106+
name: "cgroup1 max value",
107+
content: "-1",
108+
expected: math.MaxUint64,
109+
},
110+
{
111+
name: "valid value",
112+
content: "100000",
113+
expected: 100_000,
114+
},
115+
}
116+
117+
for _, s := range scenarios {
118+
t.Run(s.name, func(t *testing.T) {
119+
f, err := os.CreateTemp("", "cgroup_test*")
120+
if err != nil {
121+
t.Fatal(err)
122+
}
123+
124+
if _, err := f.Write([]byte(s.content)); err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
v, err := ReadSingleValue(f.Name())
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
133+
assert.Equal(t, s.expected, v)
134+
})
135+
}
136+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package v1
6+
7+
import (
8+
"path/filepath"
9+
10+
"github.com/gitpod-io/gitpod/common-go/cgroups"
11+
)
12+
13+
type Cpu struct {
14+
path string
15+
}
16+
17+
func NewCpuControllerWithMount(mountPoint, path string) *Cpu {
18+
fullPath := filepath.Join(mountPoint, "cpu", path)
19+
return &Cpu{
20+
path: fullPath,
21+
}
22+
}
23+
24+
func NewCpuController(path string) *Cpu {
25+
path = filepath.Join(cgroups.DefaultMountPoint, "cpu", path)
26+
return &Cpu{
27+
path: path,
28+
}
29+
}
30+
31+
// Quota returns the cpu quota in microseconds
32+
func (c *Cpu) Quota() (uint64, error) {
33+
path := filepath.Join(c.path, "cpu.cfs_quota_us")
34+
return cgroups.ReadSingleValue(path)
35+
}
36+
37+
// Period returns the cpu period in microseconds
38+
func (c *Cpu) Period() (uint64, error) {
39+
path := filepath.Join(c.path, "cpu.cfs_period_us")
40+
return cgroups.ReadSingleValue(path)
41+
}
42+
43+
// Usage returns the cpu usage in nanoseconds
44+
func (c *Cpu) Usage() (uint64, error) {
45+
path := filepath.Join(c.path, "cpuacct.usage")
46+
return cgroups.ReadSingleValue(path)
47+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package v1
6+
7+
import (
8+
"path/filepath"
9+
10+
"github.com/gitpod-io/gitpod/common-go/cgroups"
11+
)
12+
13+
type Memory struct {
14+
path string
15+
}
16+
17+
func NewMemoryControllerWithMount(mountPoint, path string) *Memory {
18+
fullPath := filepath.Join(mountPoint, "memory", path)
19+
return &Memory{
20+
path: fullPath,
21+
}
22+
}
23+
24+
func NewMemoryController(path string) *Memory {
25+
path = filepath.Join(cgroups.DefaultMountPoint, "memory", path)
26+
return &Memory{
27+
path: path,
28+
}
29+
}
30+
31+
// Limit returns the memory limit in bytes
32+
func (m *Memory) Limit() (uint64, error) {
33+
path := filepath.Join(m.path, "memory.limit_in_bytes")
34+
return cgroups.ReadSingleValue(path)
35+
}
36+
37+
// Usage returns the memory usage in bytes
38+
func (m *Memory) Usage() (uint64, error) {
39+
path := filepath.Join(m.path, "memory.usage_in_bytes")
40+
return cgroups.ReadSingleValue(path)
41+
}
42+
43+
// Stat returns cpu statistics
44+
func (m *Memory) Stat() (*cgroups.MemoryStats, error) {
45+
path := filepath.Join(m.path, "memory.stat")
46+
statMap, err := cgroups.ReadFlatKeyedFile(path)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
return &cgroups.MemoryStats{
52+
InactiveFileTotal: statMap["total_inactive_file"],
53+
}, nil
54+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package v2
6+
7+
import (
8+
"math"
9+
"os"
10+
"path/filepath"
11+
"strconv"
12+
"strings"
13+
14+
"github.com/gitpod-io/gitpod/common-go/cgroups"
15+
"golang.org/x/xerrors"
16+
)
17+
18+
const (
19+
StatUsageTotal = "usage_usec"
20+
StatUsageUser = "user_usec"
21+
StatUsageSystem = "system_usec"
22+
)
23+
24+
type Cpu struct {
25+
path string
26+
}
27+
28+
func NewCpuControllerWithMount(mountPoint, path string) *Cpu {
29+
fullPath := filepath.Join(mountPoint, path)
30+
return &Cpu{
31+
path: fullPath,
32+
}
33+
}
34+
35+
func NewCpuController(path string) *Cpu {
36+
return &Cpu{
37+
path: path,
38+
}
39+
}
40+
41+
// Max return the quota and period in microseconds
42+
func (c *Cpu) Max() (quota uint64, period uint64, err error) {
43+
path := filepath.Join(c.path, "cpu.max")
44+
content, err := os.ReadFile(path)
45+
if err != nil {
46+
return 0, 0, nil
47+
}
48+
49+
values := strings.Split(strings.TrimSpace(string(content)), " ")
50+
if len(values) < 2 {
51+
return 0, 0, xerrors.Errorf("%s has less than 2 values", path)
52+
}
53+
54+
if values[0] == "max" {
55+
quota = math.MaxUint64
56+
} else {
57+
quota, err = strconv.ParseUint(values[0], 10, 64)
58+
if err != nil {
59+
return 0, 0, err
60+
}
61+
}
62+
63+
period, err = strconv.ParseUint(values[1], 10, 64)
64+
if err != nil {
65+
return 0, 0, err
66+
}
67+
68+
return quota, period, nil
69+
}
70+
71+
// Stat returns cpu statistics (all values are in microseconds)
72+
func (c *Cpu) Stat() (*cgroups.CpuStats, error) {
73+
path := filepath.Join(c.path, "cpu.stat")
74+
statMap, err := cgroups.ReadFlatKeyedFile(path)
75+
if err != nil {
76+
return nil, err
77+
}
78+
79+
stats := cgroups.CpuStats{
80+
UsageTotal: statMap[StatUsageTotal],
81+
UsageUser: statMap[StatUsageUser],
82+
UsageSystem: statMap[StatUsageSystem],
83+
}
84+
85+
return &stats, nil
86+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package v2
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestMax(t *testing.T) {
16+
mountPoint := createMaxFile(t)
17+
18+
cpu := NewCpuControllerWithMount(mountPoint, "cgroup")
19+
quota, period, err := cpu.Max()
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
assert.Equal(t, uint64(200_000), quota)
25+
assert.Equal(t, uint64(100_000), period)
26+
}
27+
28+
func createMaxFile(t *testing.T) string {
29+
mountPoint, err := os.MkdirTemp("", "test.max")
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
cgroupPath := filepath.Join(mountPoint, "cgroup")
34+
if err := os.MkdirAll(cgroupPath, 0755); err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
if err := os.WriteFile(filepath.Join(cgroupPath, "cpu.max"), []byte("200000 100000\n"), 0755); err != nil {
39+
t.Fatalf("failed to create cpu.max file: %v", err)
40+
}
41+
42+
return mountPoint
43+
}

0 commit comments

Comments
 (0)