github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/mergeCode/runc/libcontainer/cgroups/fs/memory.go (about)

     1  // +build linux
     2  
     3  package fs
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  
    15  	"github.com/opencontainers/runc/libcontainer/cgroups"
    16  	"github.com/opencontainers/runc/libcontainer/configs"
    17  )
    18  
    19  const cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes"
    20  
    21  type MemoryGroup struct {
    22  }
    23  
    24  func (s *MemoryGroup) Name() string {
    25  	return "memory"
    26  }
    27  
    28  func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
    29  	path, err := d.path("memory")
    30  	if err != nil && !cgroups.IsNotFound(err) {
    31  		return err
    32  	}
    33  	if memoryAssigned(d.config) {
    34  		if path != "" {
    35  			if err := os.MkdirAll(path, 0755); err != nil {
    36  				return err
    37  			}
    38  		}
    39  		if d.config.KernelMemory != 0 {
    40  			if err := EnableKernelMemoryAccounting(path); err != nil {
    41  				return err
    42  			}
    43  		}
    44  	}
    45  	defer func() {
    46  		if err != nil {
    47  			os.RemoveAll(path)
    48  		}
    49  	}()
    50  
    51  	// We need to join memory cgroup after set memory limits, because
    52  	// kmem.limit_in_bytes can only be set when the cgroup is empty.
    53  	_, err = d.join("memory")
    54  	if err != nil && !cgroups.IsNotFound(err) {
    55  		return err
    56  	}
    57  	return nil
    58  }
    59  
    60  func EnableKernelMemoryAccounting(path string) error {
    61  	// Check if kernel memory is enabled
    62  	// We have to limit the kernel memory here as it won't be accounted at all
    63  	// until a limit is set on the cgroup and limit cannot be set once the
    64  	// cgroup has children, or if there are already tasks in the cgroup.
    65  	for _, i := range []int64{1, -1} {
    66  		if err := setKernelMemory(path, i); err != nil {
    67  			return err
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  func setKernelMemory(path string, kernelMemoryLimit int64) error {
    74  	if path == "" {
    75  		return fmt.Errorf("no such directory for %s", cgroupKernelMemoryLimit)
    76  	}
    77  	if !cgroups.PathExists(filepath.Join(path, cgroupKernelMemoryLimit)) {
    78  		// kernel memory is not enabled on the system so we should do nothing
    79  		return nil
    80  	}
    81  	if err := ioutil.WriteFile(filepath.Join(path, cgroupKernelMemoryLimit), []byte(strconv.FormatInt(kernelMemoryLimit, 10)), 0700); err != nil {
    82  		// Check if the error number returned by the syscall is "EBUSY"
    83  		// The EBUSY signal is returned on attempts to write to the
    84  		// memory.kmem.limit_in_bytes file if the cgroup has children or
    85  		// once tasks have been attached to the cgroup
    86  		if pathErr, ok := err.(*os.PathError); ok {
    87  			if errNo, ok := pathErr.Err.(syscall.Errno); ok {
    88  				if errNo == syscall.EBUSY {
    89  					return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit)
    90  				}
    91  			}
    92  		}
    93  		return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err)
    94  	}
    95  	return nil
    96  }
    97  
    98  func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
    99  	// When memory and swap memory are both set, we need to handle the cases
   100  	// for updating container.
   101  	if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap > 0 {
   102  		memoryUsage, err := getMemoryData(path, "")
   103  		if err != nil {
   104  			return err
   105  		}
   106  
   107  		// When update memory limit, we should adapt the write sequence
   108  		// for memory and swap memory, so it won't fail because the new
   109  		// value and the old value don't fit kernel's validation.
   110  		if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
   111  			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
   112  				return err
   113  			}
   114  			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
   115  				return err
   116  			}
   117  		} else {
   118  			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
   119  				return err
   120  			}
   121  			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
   122  				return err
   123  			}
   124  		}
   125  	} else {
   126  		if cgroup.Resources.Memory != 0 {
   127  			if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
   128  				return err
   129  			}
   130  		}
   131  		if cgroup.Resources.MemorySwap > 0 {
   132  			if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
   133  				return err
   134  			}
   135  		}
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
   142  	if err := setMemoryAndSwap(path, cgroup); err != nil {
   143  		return err
   144  	}
   145  
   146  	if cgroup.Resources.KernelMemory != 0 {
   147  		if err := setKernelMemory(path, cgroup.Resources.KernelMemory); err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	if cgroup.Resources.MemoryReservation != 0 {
   153  		if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	if cgroup.Resources.KernelMemoryTCP != 0 {
   159  		if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil {
   160  			return err
   161  		}
   162  	}
   163  	if cgroup.Resources.OomKillDisable {
   164  		if err := writeFile(path, "memory.oom_control", "1"); err != nil {
   165  			return err
   166  		}
   167  	}
   168  	if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
   169  		return nil
   170  	} else if int64(*cgroup.Resources.MemorySwappiness) >= 0 && int64(*cgroup.Resources.MemorySwappiness) <= 100 {
   171  		if err := writeFile(path, "memory.swappiness", strconv.FormatInt(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
   172  			return err
   173  		}
   174  	} else {
   175  		return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", int64(*cgroup.Resources.MemorySwappiness))
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func (s *MemoryGroup) Remove(d *cgroupData) error {
   182  	return removePath(d.path("memory"))
   183  }
   184  
   185  func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
   186  	// Set stats from memory.stat.
   187  	statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
   188  	if err != nil {
   189  		if os.IsNotExist(err) {
   190  			return nil
   191  		}
   192  		return err
   193  	}
   194  	defer statsFile.Close()
   195  
   196  	sc := bufio.NewScanner(statsFile)
   197  	for sc.Scan() {
   198  		t, v, err := getCgroupParamKeyValue(sc.Text())
   199  		if err != nil {
   200  			return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
   201  		}
   202  		stats.MemoryStats.Stats[t] = v
   203  	}
   204  	stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
   205  
   206  	memoryUsage, err := getMemoryData(path, "")
   207  	if err != nil {
   208  		return err
   209  	}
   210  	stats.MemoryStats.Usage = memoryUsage
   211  	swapUsage, err := getMemoryData(path, "memsw")
   212  	if err != nil {
   213  		return err
   214  	}
   215  	stats.MemoryStats.SwapUsage = swapUsage
   216  	kernelUsage, err := getMemoryData(path, "kmem")
   217  	if err != nil {
   218  		return err
   219  	}
   220  	stats.MemoryStats.KernelUsage = kernelUsage
   221  	kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
   222  	if err != nil {
   223  		return err
   224  	}
   225  	stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
   226  
   227  	return nil
   228  }
   229  
   230  func memoryAssigned(cgroup *configs.Cgroup) bool {
   231  	return cgroup.Resources.Memory != 0 ||
   232  		cgroup.Resources.MemoryReservation != 0 ||
   233  		cgroup.Resources.MemorySwap > 0 ||
   234  		cgroup.Resources.KernelMemory > 0 ||
   235  		cgroup.Resources.KernelMemoryTCP > 0 ||
   236  		cgroup.Resources.OomKillDisable ||
   237  		(cgroup.Resources.MemorySwappiness != nil && *cgroup.Resources.MemorySwappiness != -1)
   238  }
   239  
   240  func getMemoryData(path, name string) (cgroups.MemoryData, error) {
   241  	memoryData := cgroups.MemoryData{}
   242  
   243  	moduleName := "memory"
   244  	if name != "" {
   245  		moduleName = strings.Join([]string{"memory", name}, ".")
   246  	}
   247  	usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
   248  	maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
   249  	failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
   250  	limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
   251  
   252  	value, err := getCgroupParamUint(path, usage)
   253  	if err != nil {
   254  		if moduleName != "memory" && os.IsNotExist(err) {
   255  			return cgroups.MemoryData{}, nil
   256  		}
   257  		return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
   258  	}
   259  	memoryData.Usage = value
   260  	value, err = getCgroupParamUint(path, maxUsage)
   261  	if err != nil {
   262  		if moduleName != "memory" && os.IsNotExist(err) {
   263  			return cgroups.MemoryData{}, nil
   264  		}
   265  		return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
   266  	}
   267  	memoryData.MaxUsage = value
   268  	value, err = getCgroupParamUint(path, failcnt)
   269  	if err != nil {
   270  		if moduleName != "memory" && os.IsNotExist(err) {
   271  			return cgroups.MemoryData{}, nil
   272  		}
   273  		return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
   274  	}
   275  	memoryData.Failcnt = value
   276  	value, err = getCgroupParamUint(path, limit)
   277  	if err != nil {
   278  		if moduleName != "memory" && os.IsNotExist(err) {
   279  			return cgroups.MemoryData{}, nil
   280  		}
   281  		return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
   282  	}
   283  	memoryData.Limit = value
   284  
   285  	return memoryData, nil
   286  }