...

Source file src/github.com/containerd/cgroups/cgroup.go

Documentation: github.com/containerd/cgroups

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    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  // New returns a new control via the cgroup cgroups interface
    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  		// check if subsystem exists
    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  // Load will load an existing cgroup and allow it to be controlled
    70  // All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name
    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  	// check that the subsystems still exist, and keep only those that actually exist
    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  	// if we do not have any active systems then the cgroup is deleted
   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  // New returns a new sub cgroup
   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  // Subsystems returns all the subsystems that are currently being
   148  // consumed by the group
   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  // Add moves the provided process into the new cgroup.
   172  // Without additional arguments, the process is added to all the cgroup subsystems.
   173  // When giving Add a list of subsystem names, the process is only added to those
   174  // subsystems, provided that they are active in the targeted cgroup.
   175  func (c *cgroup) Add(process Process, subsystems ...Name) error {
   176  	return c.add(process, cgroupProcs, subsystems...)
   177  }
   178  
   179  // AddProc moves the provided process id into the new cgroup.
   180  // Without additional arguments, the process with the given id is added to all
   181  // the cgroup subsystems. When giving AddProc a list of subsystem names, the process
   182  // id is only added to those subsystems, provided that they are active in the targeted
   183  // cgroup.
   184  func (c *cgroup) AddProc(pid uint64, subsystems ...Name) error {
   185  	return c.add(Process{Pid: int(pid)}, cgroupProcs, subsystems...)
   186  }
   187  
   188  // AddTask moves the provided tasks (threads) into the new cgroup.
   189  // Without additional arguments, the task is added to all the cgroup subsystems.
   190  // When giving AddTask a list of subsystem names, the task is only added to those
   191  // subsystems, provided that they are active in the targeted cgroup.
   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  // Delete will remove the control group from each of the subsystems registered
   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  		// kernel prevents cgroups with running process from being removed, check the tree is empty
   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  // Stat returns the current metrics for the cgroup
   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  // Update updates the cgroup with the new resource values provided
   317  //
   318  // Be prepared to handle EBUSY when trying to update a cgroup with
   319  // live processes and other operations like Stats being performed at the
   320  // same time
   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  // Processes returns the processes running inside the cgroup along
   342  // with the subsystem used, pid, and path
   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  // Tasks returns the tasks running inside the cgroup along
   353  // with the subsystem used, pid, and path
   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  // Freeze freezes the entire cgroup and all the processes inside it
   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  // Thaw thaws out the cgroup and all the processes inside it
   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  // OOMEventFD returns the memory cgroup's out of memory event fd that triggers
   435  // when processes inside the cgroup receive an oom event. Returns
   436  // ErrMemoryNotSupported if memory cgroups is not supported.
   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  // RegisterMemoryEvent allows the ability to register for all v1 memory cgroups
   455  // notifications.
   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  // State returns the state of the cgroup and its processes
   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  // MoveTo does a recursive move subsystem by subsystem of all the processes
   497  // inside the group
   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