Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/cpulimit/cfs_v2.go
2500 views
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 cpulimit
6
7
import (
8
"bufio"
9
"errors"
10
"io/fs"
11
"math"
12
"os"
13
"path/filepath"
14
"strconv"
15
"strings"
16
"time"
17
18
"golang.org/x/xerrors"
19
)
20
21
type CgroupV2CFSController string
22
23
func (basePath CgroupV2CFSController) Usage() (CPUTime, error) {
24
usage, err := basePath.getFlatKeyedValue("usage_usec")
25
if err != nil {
26
return 0, err
27
}
28
29
return CPUTime(time.Duration(usage) * time.Microsecond), nil
30
}
31
32
func (basePath CgroupV2CFSController) SetLimit(limit Bandwidth) (changed bool, err error) {
33
quota, period, err := basePath.readCpuMax()
34
if err != nil {
35
return false, xerrors.Errorf("failed to read cpu max from %s: %w", basePath, err)
36
}
37
38
target := limit.Quota(period)
39
if quota == target {
40
return false, nil
41
}
42
43
err = basePath.writeQuota(target)
44
if err != nil {
45
return false, xerrors.Errorf("cannot set CFS quota of %d (period is %d, parent quota is %d): %w",
46
target.Microseconds(), period.Microseconds(), basePath.readParentQuota().Microseconds(), err)
47
}
48
49
return true, nil
50
}
51
52
func (basePath CgroupV2CFSController) NrThrottled() (uint64, error) {
53
throttled, err := basePath.getFlatKeyedValue("nr_throttled")
54
if err != nil {
55
return 0, err
56
}
57
58
return uint64(throttled), nil
59
}
60
61
func (basePath CgroupV2CFSController) readCpuMax() (time.Duration, time.Duration, error) {
62
cpuMaxPath := filepath.Join(string(basePath), "cpu.max")
63
cpuMax, err := os.ReadFile(cpuMaxPath)
64
if err != nil {
65
return 0, 0, xerrors.Errorf("unable to read cpu.max: %w", err)
66
}
67
68
parts := strings.Fields(string(cpuMax))
69
if len(parts) < 2 {
70
return 0, 0, xerrors.Errorf("cpu.max did not have expected number of fields: %s", parts)
71
}
72
73
var quota int64
74
if parts[0] == "max" {
75
quota = math.MaxInt64
76
} else {
77
quota, err = strconv.ParseInt(parts[0], 10, 64)
78
if err != nil {
79
return 0, 0, xerrors.Errorf("could not parse quota of %s: %w", parts[0], err)
80
}
81
}
82
83
period, err := strconv.ParseInt(parts[1], 10, 64)
84
if err != nil {
85
return 0, 0, xerrors.Errorf("could not parse period of %s: %w", parts[1], err)
86
}
87
88
return time.Duration(quota) * time.Microsecond, time.Duration(period) * time.Microsecond, nil
89
}
90
91
func (basePath CgroupV2CFSController) writeQuota(quota time.Duration) error {
92
cpuMaxPath := filepath.Join(string(basePath), "cpu.max")
93
return os.WriteFile(cpuMaxPath, []byte(strconv.FormatInt(quota.Microseconds(), 10)), 0644)
94
}
95
96
func (basePath CgroupV2CFSController) readParentQuota() time.Duration {
97
parent := CgroupV2CFSController(filepath.Dir(string(basePath)))
98
quota, _, err := parent.readCpuMax()
99
if err != nil {
100
return time.Duration(0)
101
}
102
103
return time.Duration(quota)
104
}
105
106
func (basePath CgroupV2CFSController) getFlatKeyedValue(key string) (int64, error) {
107
stats, err := os.Open(filepath.Join(string(basePath), "cpu.stat"))
108
if err != nil {
109
if errors.Is(err, fs.ErrNotExist) {
110
return 0, nil
111
}
112
113
return 0, xerrors.Errorf("cannot read cpu.stat: %w", err)
114
}
115
defer stats.Close()
116
117
scanner := bufio.NewScanner(stats)
118
for scanner.Scan() {
119
entry := scanner.Text()
120
if !strings.HasPrefix(entry, key) {
121
continue
122
}
123
124
r, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(entry, key)), 10, 64)
125
if err != nil {
126
return 0, xerrors.Errorf("cannot parse cpu.stat: %s: %w", entry, err)
127
}
128
return int64(r), nil
129
}
130
return 0, xerrors.Errorf("cpu.stat did not contain nr_throttled")
131
}
132
133