1
16
17 package cgroups
18
19 import (
20 "errors"
21 "fmt"
22 "os"
23 "path/filepath"
24 "strconv"
25 "strings"
26 "sync"
27
28 v1 "github.com/containerd/cgroups/stats/v1"
29
30 "github.com/opencontainers/runtime-spec/specs-go"
31 )
32
33
34 func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
35 config := newInitConfig()
36 for _, o := range opts {
37 if err := o(config); err != nil {
38 return nil, err
39 }
40 }
41 subsystems, err := hierarchy()
42 if err != nil {
43 return nil, err
44 }
45 var active []Subsystem
46 for _, s := range subsystems {
47
48 if err := initializeSubsystem(s, path, resources); err != nil {
49 if err == ErrControllerNotActive {
50 if config.InitCheck != nil {
51 if skerr := config.InitCheck(s, path, err); skerr != nil {
52 if skerr != ErrIgnoreSubsystem {
53 return nil, skerr
54 }
55 }
56 }
57 continue
58 }
59 return nil, err
60 }
61 active = append(active, s)
62 }
63 return &cgroup{
64 path: path,
65 subsystems: active,
66 }, nil
67 }
68
69
70
71 func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
72 config := newInitConfig()
73 for _, o := range opts {
74 if err := o(config); err != nil {
75 return nil, err
76 }
77 }
78 var activeSubsystems []Subsystem
79 subsystems, err := hierarchy()
80 if err != nil {
81 return nil, err
82 }
83
84 for _, s := range pathers(subsystems) {
85 p, err := path(s.Name())
86 if err != nil {
87 if errors.Is(err, os.ErrNotExist) {
88 return nil, ErrCgroupDeleted
89 }
90 if err == ErrControllerNotActive {
91 if config.InitCheck != nil {
92 if skerr := config.InitCheck(s, path, err); skerr != nil {
93 if skerr != ErrIgnoreSubsystem {
94 return nil, skerr
95 }
96 }
97 }
98 continue
99 }
100 return nil, err
101 }
102 if _, err := os.Lstat(s.Path(p)); err != nil {
103 if os.IsNotExist(err) {
104 continue
105 }
106 return nil, err
107 }
108 activeSubsystems = append(activeSubsystems, s)
109 }
110
111 if len(activeSubsystems) == 0 {
112 return nil, ErrCgroupDeleted
113 }
114 return &cgroup{
115 path: path,
116 subsystems: activeSubsystems,
117 }, nil
118 }
119
120 type cgroup struct {
121 path Path
122
123 subsystems []Subsystem
124 mu sync.Mutex
125 err error
126 }
127
128
129 func (c *cgroup) New(name string, resources *specs.LinuxResources) (Cgroup, error) {
130 c.mu.Lock()
131 defer c.mu.Unlock()
132 if c.err != nil {
133 return nil, c.err
134 }
135 path := subPath(c.path, name)
136 for _, s := range c.subsystems {
137 if err := initializeSubsystem(s, path, resources); err != nil {
138 return nil, err
139 }
140 }
141 return &cgroup{
142 path: path,
143 subsystems: c.subsystems,
144 }, nil
145 }
146
147
148
149 func (c *cgroup) Subsystems() []Subsystem {
150 return c.subsystems
151 }
152
153 func (c *cgroup) subsystemsFilter(subsystems ...Name) []Subsystem {
154 if len(subsystems) == 0 {
155 return c.subsystems
156 }
157
158 var filteredSubsystems = []Subsystem{}
159 for _, s := range c.subsystems {
160 for _, f := range subsystems {
161 if s.Name() == f {
162 filteredSubsystems = append(filteredSubsystems, s)
163 break
164 }
165 }
166 }
167
168 return filteredSubsystems
169 }
170
171
172
173
174
175 func (c *cgroup) Add(process Process, subsystems ...Name) error {
176 return c.add(process, cgroupProcs, subsystems...)
177 }
178
179
180
181
182
183
184 func (c *cgroup) AddProc(pid uint64, subsystems ...Name) error {
185 return c.add(Process{Pid: int(pid)}, cgroupProcs, subsystems...)
186 }
187
188
189
190
191
192 func (c *cgroup) AddTask(process Process, subsystems ...Name) error {
193 return c.add(process, cgroupTasks, subsystems...)
194 }
195
196 func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error {
197 if process.Pid <= 0 {
198 return ErrInvalidPid
199 }
200 c.mu.Lock()
201 defer c.mu.Unlock()
202 if c.err != nil {
203 return c.err
204 }
205 for _, s := range pathers(c.subsystemsFilter(subsystems...)) {
206 p, err := c.path(s.Name())
207 if err != nil {
208 return err
209 }
210 err = retryingWriteFile(
211 filepath.Join(s.Path(p), pType),
212 []byte(strconv.Itoa(process.Pid)),
213 defaultFilePerm,
214 )
215 if err != nil {
216 return err
217 }
218 }
219 return nil
220 }
221
222
223 func (c *cgroup) Delete() error {
224 c.mu.Lock()
225 defer c.mu.Unlock()
226 if c.err != nil {
227 return c.err
228 }
229 var errs []string
230 for _, s := range c.subsystems {
231
232 procs, err := c.processes(s.Name(), true, cgroupProcs)
233 if err != nil {
234 return err
235 }
236 if len(procs) > 0 {
237 errs = append(errs, fmt.Sprintf("%s (contains running processes)", string(s.Name())))
238 continue
239 }
240 if d, ok := s.(deleter); ok {
241 sp, err := c.path(s.Name())
242 if err != nil {
243 return err
244 }
245 if err := d.Delete(sp); err != nil {
246 errs = append(errs, string(s.Name()))
247 }
248 continue
249 }
250 if p, ok := s.(pather); ok {
251 sp, err := c.path(s.Name())
252 if err != nil {
253 return err
254 }
255 path := p.Path(sp)
256 if err := remove(path); err != nil {
257 errs = append(errs, path)
258 }
259 continue
260 }
261 }
262 if len(errs) > 0 {
263 return fmt.Errorf("cgroups: unable to remove paths %s", strings.Join(errs, ", "))
264 }
265 c.err = ErrCgroupDeleted
266 return nil
267 }
268
269
270 func (c *cgroup) Stat(handlers ...ErrorHandler) (*v1.Metrics, error) {
271 c.mu.Lock()
272 defer c.mu.Unlock()
273 if c.err != nil {
274 return nil, c.err
275 }
276 if len(handlers) == 0 {
277 handlers = append(handlers, errPassthrough)
278 }
279 var (
280 stats = &v1.Metrics{
281 CPU: &v1.CPUStat{
282 Throttling: &v1.Throttle{},
283 Usage: &v1.CPUUsage{},
284 },
285 }
286 wg = &sync.WaitGroup{}
287 errs = make(chan error, len(c.subsystems))
288 )
289 for _, s := range c.subsystems {
290 if ss, ok := s.(stater); ok {
291 sp, err := c.path(s.Name())
292 if err != nil {
293 return nil, err
294 }
295 wg.Add(1)
296 go func() {
297 defer wg.Done()
298 if err := ss.Stat(sp, stats); err != nil {
299 for _, eh := range handlers {
300 if herr := eh(err); herr != nil {
301 errs <- herr
302 }
303 }
304 }
305 }()
306 }
307 }
308 wg.Wait()
309 close(errs)
310 for err := range errs {
311 return nil, err
312 }
313 return stats, nil
314 }
315
316
317
318
319
320
321 func (c *cgroup) Update(resources *specs.LinuxResources) error {
322 c.mu.Lock()
323 defer c.mu.Unlock()
324 if c.err != nil {
325 return c.err
326 }
327 for _, s := range c.subsystems {
328 if u, ok := s.(updater); ok {
329 sp, err := c.path(s.Name())
330 if err != nil {
331 return err
332 }
333 if err := u.Update(sp, resources); err != nil {
334 return err
335 }
336 }
337 }
338 return nil
339 }
340
341
342
343 func (c *cgroup) Processes(subsystem Name, recursive bool) ([]Process, error) {
344 c.mu.Lock()
345 defer c.mu.Unlock()
346 if c.err != nil {
347 return nil, c.err
348 }
349 return c.processes(subsystem, recursive, cgroupProcs)
350 }
351
352
353
354 func (c *cgroup) Tasks(subsystem Name, recursive bool) ([]Task, error) {
355 c.mu.Lock()
356 defer c.mu.Unlock()
357 if c.err != nil {
358 return nil, c.err
359 }
360 return c.processes(subsystem, recursive, cgroupTasks)
361 }
362
363 func (c *cgroup) processes(subsystem Name, recursive bool, pType procType) ([]Process, error) {
364 s := c.getSubsystem(subsystem)
365 sp, err := c.path(subsystem)
366 if err != nil {
367 return nil, err
368 }
369 if s == nil {
370 return nil, fmt.Errorf("cgroups: %s doesn't exist in %s subsystem", sp, subsystem)
371 }
372 path := s.(pather).Path(sp)
373 var processes []Process
374 err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
375 if err != nil {
376 return err
377 }
378 if !recursive && info.IsDir() {
379 if p == path {
380 return nil
381 }
382 return filepath.SkipDir
383 }
384 dir, name := filepath.Split(p)
385 if name != pType {
386 return nil
387 }
388 procs, err := readPids(dir, subsystem, pType)
389 if err != nil {
390 return err
391 }
392 processes = append(processes, procs...)
393 return nil
394 })
395 return processes, err
396 }
397
398
399 func (c *cgroup) Freeze() error {
400 c.mu.Lock()
401 defer c.mu.Unlock()
402 if c.err != nil {
403 return c.err
404 }
405 s := c.getSubsystem(Freezer)
406 if s == nil {
407 return ErrFreezerNotSupported
408 }
409 sp, err := c.path(Freezer)
410 if err != nil {
411 return err
412 }
413 return s.(*freezerController).Freeze(sp)
414 }
415
416
417 func (c *cgroup) Thaw() error {
418 c.mu.Lock()
419 defer c.mu.Unlock()
420 if c.err != nil {
421 return c.err
422 }
423 s := c.getSubsystem(Freezer)
424 if s == nil {
425 return ErrFreezerNotSupported
426 }
427 sp, err := c.path(Freezer)
428 if err != nil {
429 return err
430 }
431 return s.(*freezerController).Thaw(sp)
432 }
433
434
435
436
437 func (c *cgroup) OOMEventFD() (uintptr, error) {
438 c.mu.Lock()
439 defer c.mu.Unlock()
440 if c.err != nil {
441 return 0, c.err
442 }
443 s := c.getSubsystem(Memory)
444 if s == nil {
445 return 0, ErrMemoryNotSupported
446 }
447 sp, err := c.path(Memory)
448 if err != nil {
449 return 0, err
450 }
451 return s.(*memoryController).memoryEvent(sp, OOMEvent())
452 }
453
454
455
456 func (c *cgroup) RegisterMemoryEvent(event MemoryEvent) (uintptr, error) {
457 c.mu.Lock()
458 defer c.mu.Unlock()
459 if c.err != nil {
460 return 0, c.err
461 }
462 s := c.getSubsystem(Memory)
463 if s == nil {
464 return 0, ErrMemoryNotSupported
465 }
466 sp, err := c.path(Memory)
467 if err != nil {
468 return 0, err
469 }
470 return s.(*memoryController).memoryEvent(sp, event)
471 }
472
473
474 func (c *cgroup) State() State {
475 c.mu.Lock()
476 defer c.mu.Unlock()
477 c.checkExists()
478 if c.err != nil && c.err == ErrCgroupDeleted {
479 return Deleted
480 }
481 s := c.getSubsystem(Freezer)
482 if s == nil {
483 return Thawed
484 }
485 sp, err := c.path(Freezer)
486 if err != nil {
487 return Unknown
488 }
489 state, err := s.(*freezerController).state(sp)
490 if err != nil {
491 return Unknown
492 }
493 return state
494 }
495
496
497
498 func (c *cgroup) MoveTo(destination Cgroup) error {
499 c.mu.Lock()
500 defer c.mu.Unlock()
501 if c.err != nil {
502 return c.err
503 }
504 for _, s := range c.subsystems {
505 processes, err := c.processes(s.Name(), true, cgroupProcs)
506 if err != nil {
507 return err
508 }
509 for _, p := range processes {
510 if err := destination.Add(p); err != nil {
511 if strings.Contains(err.Error(), "no such process") {
512 continue
513 }
514 return err
515 }
516 }
517 }
518 return nil
519 }
520
521 func (c *cgroup) getSubsystem(n Name) Subsystem {
522 for _, s := range c.subsystems {
523 if s.Name() == n {
524 return s
525 }
526 }
527 return nil
528 }
529
530 func (c *cgroup) checkExists() {
531 for _, s := range pathers(c.subsystems) {
532 p, err := c.path(s.Name())
533 if err != nil {
534 return
535 }
536 if _, err := os.Lstat(s.Path(p)); err != nil {
537 if os.IsNotExist(err) {
538 c.err = ErrCgroupDeleted
539 return
540 }
541 }
542 }
543 }
544
View as plain text