...

Source file src/github.com/opencontainers/runc/libcontainer/cgroups/fs2/memory.go

Documentation: github.com/opencontainers/runc/libcontainer/cgroups/fs2

     1  package fs2
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"math"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"golang.org/x/sys/unix"
    12  
    13  	"github.com/opencontainers/runc/libcontainer/cgroups"
    14  	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
    15  	"github.com/opencontainers/runc/libcontainer/configs"
    16  )
    17  
    18  // numToStr converts an int64 value to a string for writing to a
    19  // cgroupv2 files with .min, .max, .low, or .high suffix.
    20  // The value of -1 is converted to "max" for cgroupv1 compatibility
    21  // (which used to write -1 to remove the limit).
    22  func numToStr(value int64) (ret string) {
    23  	switch {
    24  	case value == 0:
    25  		ret = ""
    26  	case value == -1:
    27  		ret = "max"
    28  	default:
    29  		ret = strconv.FormatInt(value, 10)
    30  	}
    31  
    32  	return ret
    33  }
    34  
    35  func isMemorySet(r *configs.Resources) bool {
    36  	return r.MemoryReservation != 0 || r.Memory != 0 || r.MemorySwap != 0
    37  }
    38  
    39  func setMemory(dirPath string, r *configs.Resources) error {
    40  	if !isMemorySet(r) {
    41  		return nil
    42  	}
    43  	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(r.MemorySwap, r.Memory)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	swapStr := numToStr(swap)
    48  	if swapStr == "" && swap == 0 && r.MemorySwap > 0 {
    49  		// memory and memorySwap set to the same value -- disable swap
    50  		swapStr = "0"
    51  	}
    52  	// never write empty string to `memory.swap.max`, it means set to 0.
    53  	if swapStr != "" {
    54  		if err := cgroups.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
    55  			return err
    56  		}
    57  	}
    58  
    59  	if val := numToStr(r.Memory); val != "" {
    60  		if err := cgroups.WriteFile(dirPath, "memory.max", val); err != nil {
    61  			return err
    62  		}
    63  	}
    64  
    65  	// cgroup.Resources.KernelMemory is ignored
    66  
    67  	if val := numToStr(r.MemoryReservation); val != "" {
    68  		if err := cgroups.WriteFile(dirPath, "memory.low", val); err != nil {
    69  			return err
    70  		}
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  func statMemory(dirPath string, stats *cgroups.Stats) error {
    77  	const file = "memory.stat"
    78  	statsFile, err := cgroups.OpenFile(dirPath, file, os.O_RDONLY)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	defer statsFile.Close()
    83  
    84  	sc := bufio.NewScanner(statsFile)
    85  	for sc.Scan() {
    86  		t, v, err := fscommon.ParseKeyValue(sc.Text())
    87  		if err != nil {
    88  			return &parseError{Path: dirPath, File: file, Err: err}
    89  		}
    90  		stats.MemoryStats.Stats[t] = v
    91  	}
    92  	if err := sc.Err(); err != nil {
    93  		return &parseError{Path: dirPath, File: file, Err: err}
    94  	}
    95  	stats.MemoryStats.Cache = stats.MemoryStats.Stats["file"]
    96  	// Unlike cgroup v1 which has memory.use_hierarchy binary knob,
    97  	// cgroup v2 is always hierarchical.
    98  	stats.MemoryStats.UseHierarchy = true
    99  
   100  	memoryUsage, err := getMemoryDataV2(dirPath, "")
   101  	if err != nil {
   102  		if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
   103  			// The root cgroup does not have memory.{current,max,peak}
   104  			// so emulate those using data from /proc/meminfo and
   105  			// /sys/fs/cgroup/memory.stat
   106  			return rootStatsFromMeminfo(stats)
   107  		}
   108  		return err
   109  	}
   110  	stats.MemoryStats.Usage = memoryUsage
   111  	swapOnlyUsage, err := getMemoryDataV2(dirPath, "swap")
   112  	if err != nil {
   113  		return err
   114  	}
   115  	stats.MemoryStats.SwapOnlyUsage = swapOnlyUsage
   116  	swapUsage := swapOnlyUsage
   117  	// As cgroup v1 reports SwapUsage values as mem+swap combined,
   118  	// while in cgroup v2 swap values do not include memory,
   119  	// report combined mem+swap for v1 compatibility.
   120  	swapUsage.Usage += memoryUsage.Usage
   121  	if swapUsage.Limit != math.MaxUint64 {
   122  		swapUsage.Limit += memoryUsage.Limit
   123  	}
   124  	// The `MaxUsage` of mem+swap cannot simply combine mem with
   125  	// swap. So set it to 0 for v1 compatibility.
   126  	swapUsage.MaxUsage = 0
   127  	stats.MemoryStats.SwapUsage = swapUsage
   128  
   129  	return nil
   130  }
   131  
   132  func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
   133  	memoryData := cgroups.MemoryData{}
   134  
   135  	moduleName := "memory"
   136  	if name != "" {
   137  		moduleName = "memory." + name
   138  	}
   139  	usage := moduleName + ".current"
   140  	limit := moduleName + ".max"
   141  	maxUsage := moduleName + ".peak"
   142  
   143  	value, err := fscommon.GetCgroupParamUint(path, usage)
   144  	if err != nil {
   145  		if name != "" && os.IsNotExist(err) {
   146  			// Ignore EEXIST as there's no swap accounting
   147  			// if kernel CONFIG_MEMCG_SWAP is not set or
   148  			// swapaccount=0 kernel boot parameter is given.
   149  			return cgroups.MemoryData{}, nil
   150  		}
   151  		return cgroups.MemoryData{}, err
   152  	}
   153  	memoryData.Usage = value
   154  
   155  	value, err = fscommon.GetCgroupParamUint(path, limit)
   156  	if err != nil {
   157  		return cgroups.MemoryData{}, err
   158  	}
   159  	memoryData.Limit = value
   160  
   161  	// `memory.peak` since kernel 5.19
   162  	// `memory.swap.peak` since kernel 6.5
   163  	value, err = fscommon.GetCgroupParamUint(path, maxUsage)
   164  	if err != nil && !os.IsNotExist(err) {
   165  		return cgroups.MemoryData{}, err
   166  	}
   167  	memoryData.MaxUsage = value
   168  
   169  	return memoryData, nil
   170  }
   171  
   172  func rootStatsFromMeminfo(stats *cgroups.Stats) error {
   173  	const file = "/proc/meminfo"
   174  	f, err := os.Open(file)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	defer f.Close()
   179  
   180  	// Fields we are interested in.
   181  	var (
   182  		swap_free  uint64
   183  		swap_total uint64
   184  	)
   185  	mem := map[string]*uint64{
   186  		"SwapFree":  &swap_free,
   187  		"SwapTotal": &swap_total,
   188  	}
   189  
   190  	found := 0
   191  	sc := bufio.NewScanner(f)
   192  	for sc.Scan() {
   193  		parts := strings.SplitN(sc.Text(), ":", 3)
   194  		if len(parts) != 2 {
   195  			// Should not happen.
   196  			continue
   197  		}
   198  		k := parts[0]
   199  		p, ok := mem[k]
   200  		if !ok {
   201  			// Unknown field -- not interested.
   202  			continue
   203  		}
   204  		vStr := strings.TrimSpace(strings.TrimSuffix(parts[1], " kB"))
   205  		*p, err = strconv.ParseUint(vStr, 10, 64)
   206  		if err != nil {
   207  			return &parseError{File: file, Err: errors.New("bad value for " + k)}
   208  		}
   209  
   210  		found++
   211  		if found == len(mem) {
   212  			// Got everything we need -- skip the rest.
   213  			break
   214  		}
   215  	}
   216  	if err := sc.Err(); err != nil {
   217  		return &parseError{Path: "", File: file, Err: err}
   218  	}
   219  
   220  	// cgroup v1 `usage_in_bytes` reports memory usage as the sum of
   221  	// - rss (NR_ANON_MAPPED)
   222  	// - cache (NR_FILE_PAGES)
   223  	// cgroup v1 reports SwapUsage values as mem+swap combined
   224  	// cgroup v2 reports rss and cache as anon and file.
   225  	// sum `anon` + `file` to report the same value as `usage_in_bytes` in v1.
   226  	// sum swap usage as combined mem+swap usage for consistency as well.
   227  	stats.MemoryStats.Usage.Usage = stats.MemoryStats.Stats["anon"] + stats.MemoryStats.Stats["file"]
   228  	stats.MemoryStats.Usage.Limit = math.MaxUint64
   229  	stats.MemoryStats.SwapUsage.Usage = (swap_total - swap_free) * 1024
   230  	stats.MemoryStats.SwapUsage.Limit = math.MaxUint64
   231  	stats.MemoryStats.SwapUsage.Usage += stats.MemoryStats.Usage.Usage
   232  
   233  	return nil
   234  }
   235  

View as plain text