1
2
3
4
5 package cgroup
6
7 import (
8 "internal/bytealg"
9 "internal/runtime/strconv"
10 "internal/runtime/syscall"
11 )
12
13 var (
14 ErrNoCgroup error = stringError("not in a cgroup")
15
16 errMalformedFile error = stringError("malformed file")
17 )
18
19 const _PATH_MAX = 4096
20
21 const (
22
23
24
25
26
27
28
29
30
31
32
33 ScratchSize = PathSize + ParseSize
34
35
36 PathSize = _PATH_MAX
37
38
39
40
41 escapedPathMax = 4 * _PATH_MAX
42
43
44
45 ParseSize = 4 * escapedPathMax
46 )
47
48
49 const (
50 v2MaxFile = "/cpu.max\x00"
51 v1QuotaFile = "/cpu.cfs_quota_us\x00"
52 v1PeriodFile = "/cpu.cfs_period_us\x00"
53 )
54
55
56 type Version int
57
58 const (
59 VersionUnknown Version = iota
60 V1
61 V2
62 )
63
64
65 type CPU struct {
66 version Version
67
68
69
70 quotaFD int
71
72
73
74 periodFD int
75 }
76
77 func (c CPU) Close() {
78 switch c.version {
79 case V1:
80 syscall.Close(c.quotaFD)
81 syscall.Close(c.periodFD)
82 case V2:
83 syscall.Close(c.quotaFD)
84 default:
85 throw("impossible cgroup version")
86 }
87 }
88
89 func checkBufferSize(s []byte, size int) {
90 if len(s) != size {
91 println("runtime: cgroup buffer length", len(s), "want", size)
92 throw("runtime: cgroup invalid buffer length")
93 }
94 }
95
96
97
98
99
100 func OpenCPU(scratch []byte) (CPU, error) {
101 checkBufferSize(scratch, ScratchSize)
102
103 base := scratch[:PathSize]
104 scratch2 := scratch[PathSize:]
105
106 n, version, err := FindCPU(base, scratch2)
107 if err != nil {
108 return CPU{}, err
109 }
110
111 switch version {
112 case 1:
113 n2 := copy(base[n:], v1QuotaFile)
114 path := base[:n+n2]
115 quotaFD, errno := syscall.Open(&path[0], syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
116 if errno != 0 {
117
118
119
120 return CPU{}, errSyscallFailed
121 }
122
123 n2 = copy(base[n:], v1PeriodFile)
124 path = base[:n+n2]
125 periodFD, errno := syscall.Open(&path[0], syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
126 if errno != 0 {
127
128
129
130 return CPU{}, errSyscallFailed
131 }
132
133 c := CPU{
134 version: 1,
135 quotaFD: quotaFD,
136 periodFD: periodFD,
137 }
138 return c, nil
139 case 2:
140 n2 := copy(base[n:], v2MaxFile)
141 path := base[:n+n2]
142 maxFD, errno := syscall.Open(&path[0], syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
143 if errno != 0 {
144
145
146
147 return CPU{}, errSyscallFailed
148 }
149
150 c := CPU{
151 version: 2,
152 quotaFD: maxFD,
153 periodFD: -1,
154 }
155 return c, nil
156 default:
157 throw("impossible cgroup version")
158 panic("unreachable")
159 }
160 }
161
162
163
164 func ReadCPULimit(c CPU) (float64, bool, error) {
165 switch c.version {
166 case 1:
167 quota, err := readV1Number(c.quotaFD)
168 if err != nil {
169 return 0, false, errMalformedFile
170 }
171
172 if quota < 0 {
173
174 return 0, false, nil
175 }
176
177 period, err := readV1Number(c.periodFD)
178 if err != nil {
179 return 0, false, errMalformedFile
180 }
181
182 return float64(quota) / float64(period), true, nil
183 case 2:
184
185 return readV2Limit(c.quotaFD)
186 default:
187 throw("impossible cgroup version")
188 panic("unreachable")
189 }
190 }
191
192
193 func readV1Number(fd int) (int64, error) {
194
195
196
197
198
199
200
201
202 var b [64]byte
203 n, errno := syscall.Pread(fd, b[:], 0)
204 if errno != 0 {
205 return 0, errSyscallFailed
206 }
207 if n == len(b) {
208 return 0, errMalformedFile
209 }
210
211 buf := b[:n]
212 return parseV1Number(buf)
213 }
214
215 func parseV1Number(buf []byte) (int64, error) {
216
217 i := bytealg.IndexByte(buf, '\n')
218 if i < 0 {
219 return 0, errMalformedFile
220 }
221 buf = buf[:i]
222
223 val, ok := strconv.Atoi64(string(buf))
224 if !ok {
225 return 0, errMalformedFile
226 }
227
228 return val, nil
229 }
230
231
232 func readV2Limit(fd int) (float64, bool, error) {
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 var b [64]byte
251 n, errno := syscall.Pread(fd, b[:], 0)
252 if errno != 0 {
253 return 0, false, errSyscallFailed
254 }
255 if n == len(b) {
256 return 0, false, errMalformedFile
257 }
258
259 buf := b[:n]
260 return parseV2Limit(buf)
261 }
262
263 func parseV2Limit(buf []byte) (float64, bool, error) {
264 i := bytealg.IndexByte(buf, ' ')
265 if i < 0 {
266 return 0, false, errMalformedFile
267 }
268
269 quotaStr := buf[:i]
270 if bytealg.Compare(quotaStr, []byte("max")) == 0 {
271
272 return 0, false, nil
273 }
274
275 periodStr := buf[i+1:]
276
277 i = bytealg.IndexByte(periodStr, '\n')
278 if i < 0 {
279 return 0, false, errMalformedFile
280 }
281 periodStr = periodStr[:i]
282
283 quota, ok := strconv.Atoi64(string(quotaStr))
284 if !ok {
285 return 0, false, errMalformedFile
286 }
287
288 period, ok := strconv.Atoi64(string(periodStr))
289 if !ok {
290 return 0, false, errMalformedFile
291 }
292
293 return float64(quota) / float64(period), true, nil
294 }
295
296
297
298
299
300
301
302
303
304 func FindCPU(out []byte, scratch []byte) (int, Version, error) {
305 checkBufferSize(out, PathSize)
306 checkBufferSize(scratch, ParseSize)
307
308
309
310
311
312
313
314
315
316
317
318
319
320 n, err := FindCPUMountPoint(out, scratch)
321 if err != nil {
322 return 0, 0, err
323 }
324
325
326
327 n2, version, err := FindCPURelativePath(out[n:], scratch)
328 if err != nil {
329 return 0, 0, err
330 }
331 n += n2
332
333 return n, version, nil
334 }
335
336
337
338
339
340
341
342
343
344
345
346 func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) {
347 path := []byte("/proc/self/cgroup\x00")
348 fd, errno := syscall.Open(&path[0], syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
349 if errno == syscall.ENOENT {
350 return 0, 0, ErrNoCgroup
351 } else if errno != 0 {
352 return 0, 0, errSyscallFailed
353 }
354
355
356
357 n, version, err := parseCPURelativePath(fd, syscall.Read, out[:], scratch)
358 if err != nil {
359 syscall.Close(fd)
360 return 0, 0, err
361 }
362
363 syscall.Close(fd)
364 return n, version, nil
365 }
366
367
368
369
370
371 func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) {
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387 l := newLineReader(fd, scratch, read)
388
389
390 n := 0
391
392 for {
393 err := l.next()
394 if err == errIncompleteLine {
395
396
397
398
399
400 return 0, 0, err
401 } else if err == errEOF {
402 break
403 } else if err != nil {
404 return 0, 0, err
405 }
406
407 line := l.line()
408
409
410
411
412
413
414
415 i := bytealg.IndexByte(line, ':')
416 if i < 0 {
417 return 0, 0, errMalformedFile
418 }
419
420 hierarchy := line[:i]
421 line = line[i+1:]
422
423 i = bytealg.IndexByte(line, ':')
424 if i < 0 {
425 return 0, 0, errMalformedFile
426 }
427
428 controllers := line[:i]
429 line = line[i+1:]
430
431 path := line
432
433 if string(hierarchy) == "0" {
434
435 n = copy(out, path)
436
437
438 } else {
439
440 if containsCPU(controllers) {
441
442
443 return copy(out, path), V1, nil
444 }
445 }
446 }
447
448 if n == 0 {
449
450 return 0, 0, ErrNoCgroup
451 }
452
453
454 return n, V2, nil
455 }
456
457
458 func containsCPU(b []byte) bool {
459 for len(b) > 0 {
460 i := bytealg.IndexByte(b, ',')
461 if i < 0 {
462
463 return string(b) == "cpu"
464 }
465
466 curr := b[:i]
467 rest := b[i+1:]
468
469 if string(curr) == "cpu" {
470 return true
471 }
472
473 b = rest
474 }
475
476 return false
477 }
478
479
480
481
482
483
484
485
486
487 func FindCPUMountPoint(out []byte, scratch []byte) (int, error) {
488 checkBufferSize(out, PathSize)
489 checkBufferSize(scratch, ParseSize)
490
491 path := []byte("/proc/self/mountinfo\x00")
492 fd, errno := syscall.Open(&path[0], syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
493 if errno == syscall.ENOENT {
494 return 0, ErrNoCgroup
495 } else if errno != 0 {
496 return 0, errSyscallFailed
497 }
498
499 n, err := parseCPUMount(fd, syscall.Read, out, scratch)
500 if err != nil {
501 syscall.Close(fd)
502 return 0, err
503 }
504 syscall.Close(fd)
505
506 return n, nil
507 }
508
509
510
511 func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) {
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543 l := newLineReader(fd, scratch, read)
544
545
546 n := 0
547
548 for {
549
550 err := l.next()
551 if err == errIncompleteLine {
552
553
554
555
556
557
558 } else if err == errEOF {
559 break
560 } else if err != nil {
561 return 0, err
562 }
563
564 line := l.line()
565
566
567 for range 4 {
568 i := bytealg.IndexByte(line, ' ')
569 if i < 0 {
570 return 0, errMalformedFile
571 }
572 line = line[i+1:]
573 }
574
575
576 i := bytealg.IndexByte(line, ' ')
577 if i < 0 {
578 return 0, errMalformedFile
579 }
580 mnt := line[:i]
581 line = line[i+1:]
582
583
584 for {
585 i = bytealg.IndexByte(line, ' ')
586 if i < 0 {
587 return 0, errMalformedFile
588 }
589 if i+3 >= len(line) {
590 return 0, errMalformedFile
591 }
592 delim := line[i : i+3]
593 if string(delim) == " - " {
594 line = line[i+3:]
595 break
596 }
597 line = line[i+1:]
598 }
599
600
601 i = bytealg.IndexByte(line, ' ')
602 if i < 0 {
603 return 0, errMalformedFile
604 }
605 ftype := line[:i]
606 line = line[i+1:]
607
608 if string(ftype) != "cgroup" && string(ftype) != "cgroup2" {
609 continue
610 }
611
612
613
614 if string(ftype) == "cgroup2" {
615
616 n, err = unescapePath(out, mnt)
617 if err != nil {
618
619
620 return n, err
621 }
622
623
624 continue
625 }
626
627
628 i = bytealg.IndexByte(line, ' ')
629 if i < 0 {
630 return 0, errMalformedFile
631 }
632
633 line = line[i+1:]
634
635
636 superOpt := line
637
638
639 if containsCPU(superOpt) {
640
641
642 return unescapePath(out, mnt)
643 }
644 }
645
646 if n == 0 {
647
648 return 0, ErrNoCgroup
649 }
650
651 return n, nil
652 }
653
654 var errInvalidEscape error = stringError("invalid path escape sequence")
655
656
657
658
659
660
661
662
663
664
665
666
667 func unescapePath(out []byte, in []byte) (int, error) {
668
669
670 if len(out) < len(in) {
671 throw("output too small")
672 }
673
674 var outi, ini int
675 for ini < len(in) {
676 c := in[ini]
677 if c != '\\' {
678 out[outi] = c
679 outi++
680 ini++
681 continue
682 }
683
684
685
686
687
688 if ini+3 >= len(in) {
689 return outi, errInvalidEscape
690 }
691
692 var outc byte
693 for i := range 3 {
694 c := in[ini+1+i]
695 if c < '0' || c > '9' {
696 return outi, errInvalidEscape
697 }
698
699 outc *= 8
700 outc += c - '0'
701 }
702
703 out[outi] = outc
704 outi++
705
706 ini += 4
707 }
708
709 return outi, nil
710 }
711
View as plain text