1
2
3
4
5 package cgroup
6
7 import (
8 "internal/runtime/syscall/linux"
9 )
10
11
12 const (
13 v2MaxFile = "/cpu.max\x00"
14 v1QuotaFile = "/cpu.cfs_quota_us\x00"
15 v1PeriodFile = "/cpu.cfs_period_us\x00"
16 )
17
18
19 type CPU struct {
20 version Version
21
22
23
24 quotaFD int
25
26
27
28 periodFD int
29 }
30
31 func (c CPU) Close() {
32 switch c.version {
33 case V1:
34 linux.Close(c.quotaFD)
35 linux.Close(c.periodFD)
36 case V2:
37 linux.Close(c.quotaFD)
38 default:
39 throw("impossible cgroup version")
40 }
41 }
42
43 func checkBufferSize(s []byte, size int) {
44 if len(s) != size {
45 println("runtime: cgroup buffer length", len(s), "want", size)
46 throw("runtime: cgroup invalid buffer length")
47 }
48 }
49
50
51
52
53
54 func OpenCPU(scratch []byte) (CPU, error) {
55 checkBufferSize(scratch, ScratchSize)
56
57 base := scratch[:PathSize]
58 scratch2 := scratch[PathSize:]
59
60 n, version, err := FindCPU(base, scratch2)
61 if err != nil {
62 return CPU{}, err
63 }
64
65 switch version {
66 case 1:
67 n2 := copy(base[n:], v1QuotaFile)
68 path := base[:n+n2]
69 quotaFD, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
70 if errno != 0 {
71
72
73
74 return CPU{}, errSyscallFailed
75 }
76
77 n2 = copy(base[n:], v1PeriodFile)
78 path = base[:n+n2]
79 periodFD, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
80 if errno != 0 {
81
82
83
84 return CPU{}, errSyscallFailed
85 }
86
87 c := CPU{
88 version: 1,
89 quotaFD: quotaFD,
90 periodFD: periodFD,
91 }
92 return c, nil
93 case 2:
94 n2 := copy(base[n:], v2MaxFile)
95 path := base[:n+n2]
96 maxFD, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
97 if errno != 0 {
98
99
100
101 return CPU{}, errSyscallFailed
102 }
103
104 c := CPU{
105 version: 2,
106 quotaFD: maxFD,
107 periodFD: -1,
108 }
109 return c, nil
110 default:
111 throw("impossible cgroup version")
112 panic("unreachable")
113 }
114 }
115
116
117
118 func ReadCPULimit(c CPU) (float64, bool, error) {
119 switch c.version {
120 case 1:
121 quota, err := readV1Number(c.quotaFD)
122 if err != nil {
123 return 0, false, errMalformedFile
124 }
125
126 if quota < 0 {
127
128 return 0, false, nil
129 }
130
131 period, err := readV1Number(c.periodFD)
132 if err != nil {
133 return 0, false, errMalformedFile
134 }
135
136 return float64(quota) / float64(period), true, nil
137 case 2:
138
139 return readV2Limit(c.quotaFD)
140 default:
141 throw("impossible cgroup version")
142 panic("unreachable")
143 }
144 }
145
146
147 func readV1Number(fd int) (int64, error) {
148
149
150
151
152
153
154
155
156 var b [64]byte
157 n, errno := linux.Pread(fd, b[:], 0)
158 if errno != 0 {
159 return 0, errSyscallFailed
160 }
161 if n == len(b) {
162 return 0, errMalformedFile
163 }
164
165 buf := b[:n]
166 return parseV1Number(buf)
167 }
168
169
170 func readV2Limit(fd int) (float64, bool, error) {
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 var b [64]byte
189 n, errno := linux.Pread(fd, b[:], 0)
190 if errno != 0 {
191 return 0, false, errSyscallFailed
192 }
193 if n == len(b) {
194 return 0, false, errMalformedFile
195 }
196
197 buf := b[:n]
198 return parseV2Limit(buf)
199 }
200
201
202
203
204
205
206
207
208
209 func FindCPU(out []byte, scratch []byte) (int, Version, error) {
210 checkBufferSize(out, PathSize)
211 checkBufferSize(scratch, ParseSize)
212
213
214
215
216 n, version, err := FindCPUCgroup(out, scratch)
217 if err != nil {
218 return 0, 0, err
219 }
220
221 n, err = FindCPUMountPoint(out, out[:n], version, scratch)
222 return n, version, err
223 }
224
225
226
227
228
229
230
231
232
233 func FindCPUCgroup(out []byte, scratch []byte) (int, Version, error) {
234 path := []byte("/proc/self/cgroup\x00")
235 fd, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
236 if errno == linux.ENOENT {
237 return 0, 0, ErrNoCgroup
238 } else if errno != 0 {
239 return 0, 0, errSyscallFailed
240 }
241
242
243
244 n, version, err := parseCPUCgroup(fd, linux.Read, out[:], scratch)
245 if err != nil {
246 linux.Close(fd)
247 return 0, 0, err
248 }
249
250 linux.Close(fd)
251 return n, version, nil
252 }
253
254
255
256
257
258
259
260
261
262
263
264 func FindCPUMountPoint(out, cgroup []byte, version Version, scratch []byte) (int, error) {
265 checkBufferSize(out, PathSize)
266 checkBufferSize(scratch, ParseSize)
267
268 path := []byte("/proc/self/mountinfo\x00")
269 fd, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0)
270 if errno == linux.ENOENT {
271 return 0, ErrNoCgroup
272 } else if errno != 0 {
273 return 0, errSyscallFailed
274 }
275
276 n, err := parseCPUMount(fd, linux.Read, out, cgroup, version, scratch)
277 if err != nil {
278 linux.Close(fd)
279 return 0, err
280 }
281 linux.Close(fd)
282
283 return n, nil
284 }
285
View as plain text