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 }