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.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
// CgroupV1CFSController controls a cgroup's CFS settings
22
type CgroupV1CFSController string
23
24
// Usage returns the cpuacct.usage value of the cgroup
25
func (basePath CgroupV1CFSController) Usage() (usage CPUTime, err error) {
26
cputime, err := basePath.readCpuUsage()
27
if err != nil {
28
return 0, xerrors.Errorf("cannot read cpuacct.usage: %w", err)
29
}
30
31
return CPUTime(cputime), nil
32
}
33
34
// SetQuota sets a new CFS quota on the cgroup
35
func (basePath CgroupV1CFSController) SetLimit(limit Bandwidth) (changed bool, err error) {
36
period, err := basePath.readCfsPeriod()
37
if err != nil {
38
err = xerrors.Errorf("failed to read CFS period: %w", err)
39
return
40
}
41
42
quota, err := basePath.readCfsQuota()
43
if err != nil {
44
err = xerrors.Errorf("cannot parse CFS quota: %w", err)
45
return
46
}
47
target := limit.Quota(period)
48
if quota == target {
49
return false, nil
50
}
51
52
err = os.WriteFile(filepath.Join(string(basePath), "cpu.cfs_quota_us"), []byte(strconv.FormatInt(target.Microseconds(), 10)), 0644)
53
if err != nil {
54
return false, xerrors.Errorf("cannot set CFS quota of %d (period is %d, parent quota is %d): %w",
55
target.Microseconds(), period.Microseconds(), basePath.readParentQuota().Microseconds(), err)
56
}
57
return true, nil
58
}
59
60
func (basePath CgroupV1CFSController) readParentQuota() time.Duration {
61
parent := CgroupV1CFSController(filepath.Dir(string(basePath)))
62
pq, err := parent.readCfsQuota()
63
if err != nil {
64
return time.Duration(0)
65
}
66
67
return time.Duration(pq) * time.Microsecond
68
}
69
70
func (basePath CgroupV1CFSController) readString(path string) (string, error) {
71
fn := filepath.Join(string(basePath), path)
72
fc, err := os.ReadFile(fn)
73
if err != nil {
74
return "", err
75
}
76
77
s := strings.TrimSpace(string(fc))
78
return s, nil
79
}
80
81
func (basePath CgroupV1CFSController) readCfsPeriod() (time.Duration, error) {
82
s, err := basePath.readString("cpu.cfs_period_us")
83
if err != nil {
84
return 0, err
85
}
86
87
p, err := strconv.ParseInt(s, 10, 64)
88
if err != nil {
89
return 0, err
90
}
91
return time.Duration(uint64(p)) * time.Microsecond, nil
92
}
93
94
func (basePath CgroupV1CFSController) readCfsQuota() (time.Duration, error) {
95
s, err := basePath.readString("cpu.cfs_quota_us")
96
if err != nil {
97
return 0, err
98
}
99
100
p, err := strconv.ParseInt(s, 10, 64)
101
if err != nil {
102
return 0, err
103
}
104
105
if p < 0 {
106
return time.Duration(math.MaxInt64), nil
107
}
108
return time.Duration(p) * time.Microsecond, nil
109
}
110
111
func (basePath CgroupV1CFSController) readCpuUsage() (time.Duration, error) {
112
s, err := basePath.readString("cpuacct.usage")
113
if err != nil {
114
return 0, err
115
}
116
117
p, err := strconv.ParseInt(s, 10, 64)
118
if err != nil {
119
return 0, err
120
}
121
return time.Duration(uint64(p)) * time.Nanosecond, nil
122
}
123
124
// NrThrottled returns the number of CFS periods the cgroup was throttled in
125
func (basePath CgroupV1CFSController) NrThrottled() (uint64, error) {
126
f, err := os.Open(filepath.Join(string(basePath), "cpu.stat"))
127
if err != nil {
128
if errors.Is(err, fs.ErrNotExist) {
129
return 0, nil
130
}
131
132
return 0, xerrors.Errorf("cannot read cpu.stat: %w", err)
133
}
134
defer f.Close()
135
136
const prefixNrThrottled = "nr_throttled "
137
138
scanner := bufio.NewScanner(f)
139
for scanner.Scan() {
140
l := scanner.Text()
141
if !strings.HasPrefix(l, prefixNrThrottled) {
142
continue
143
}
144
145
r, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(l, prefixNrThrottled)), 10, 64)
146
if err != nil {
147
return 0, xerrors.Errorf("cannot parse cpu.stat: %s: %w", l, err)
148
}
149
return uint64(r), nil
150
}
151
return 0, xerrors.Errorf("cpu.stat did not contain nr_throttled")
152
}
153
154