2015-05-14 06:42:16 +08:00
|
|
|
// +build linux
|
|
|
|
|
2014-05-15 06:21:44 +08:00
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2014-09-24 08:13:57 +08:00
|
|
|
"fmt"
|
2016-04-29 01:49:29 +08:00
|
|
|
"math"
|
2014-05-15 06:21:44 +08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2015-05-15 17:37:58 +08:00
|
|
|
"strings"
|
2014-05-28 08:01:08 +08:00
|
|
|
|
2015-06-22 10:29:59 +08:00
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
2016-04-29 01:49:29 +08:00
|
|
|
"github.com/opencontainers/runc/libcontainer/system"
|
2014-05-15 06:21:44 +08:00
|
|
|
)
|
|
|
|
|
2014-06-20 21:13:56 +08:00
|
|
|
type MemoryGroup struct {
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-10-16 06:19:23 +08:00
|
|
|
|
|
|
|
func (s *MemoryGroup) Name() string {
|
|
|
|
return "memory"
|
|
|
|
}
|
2014-05-15 06:21:44 +08:00
|
|
|
|
2015-11-05 18:41:08 +08:00
|
|
|
func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
|
2015-06-18 15:44:30 +08:00
|
|
|
path, err := d.path("memory")
|
2015-10-20 14:01:48 +08:00
|
|
|
if err != nil && !cgroups.IsNotFound(err) {
|
2015-03-24 03:14:03 +08:00
|
|
|
return err
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-11-05 18:52:14 +08:00
|
|
|
if memoryAssigned(d.config) {
|
2015-10-20 14:01:48 +08:00
|
|
|
if path != "" {
|
|
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-01-13 22:05:08 +08:00
|
|
|
// We have to set kernel memory here, as we can't change it once
|
2016-04-29 01:49:29 +08:00
|
|
|
// processes have been attached to the cgroup.
|
2016-01-13 22:05:08 +08:00
|
|
|
if err := s.SetKernelMemory(path, d.config); err != nil {
|
2015-10-20 14:01:48 +08:00
|
|
|
return err
|
|
|
|
}
|
2015-06-18 15:44:30 +08:00
|
|
|
}
|
2015-08-16 12:19:44 +08:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(path)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-06-18 15:44:30 +08:00
|
|
|
// We need to join memory cgroup after set memory limits, because
|
|
|
|
// kmem.limit_in_bytes can only be set when the cgroup is empty.
|
2015-10-20 14:01:48 +08:00
|
|
|
_, err = d.join("memory")
|
|
|
|
if err != nil && !cgroups.IsNotFound(err) {
|
2015-06-18 15:44:30 +08:00
|
|
|
return err
|
|
|
|
}
|
2016-01-13 22:05:08 +08:00
|
|
|
return nil
|
|
|
|
}
|
2014-05-15 06:21:44 +08:00
|
|
|
|
2016-01-13 22:05:08 +08:00
|
|
|
func (s *MemoryGroup) SetKernelMemory(path string, cgroup *configs.Cgroup) error {
|
2016-04-29 01:49:29 +08:00
|
|
|
// This has to be done separately because it has special
|
|
|
|
// constraints (it can only be initialized before setting up a
|
|
|
|
// hierarchy or adding a task to the cgroups. However, if
|
|
|
|
// sucessfully initialized, it can be updated anytime afterwards)
|
|
|
|
if cgroup.Resources.KernelMemory != 0 {
|
|
|
|
kmemInitialized := false
|
|
|
|
// Is kmem.limit_in_bytes already set?
|
|
|
|
kmemValue, err := getCgroupParamUint(path, "memory.kmem.limit_in_bytes")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch system.GetLongBit() {
|
|
|
|
case 32:
|
|
|
|
kmemInitialized = uint32(kmemValue) != uint32(math.MaxUint32)
|
|
|
|
case 64:
|
|
|
|
kmemInitialized = kmemValue != uint64(math.MaxUint64)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !kmemInitialized {
|
|
|
|
// If there's already tasks in the cgroup, we can't change the limit either
|
|
|
|
tasks, err := getCgroupParamString(path, "tasks")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if tasks != "" {
|
Remove use_hierarchy check when set kernel memory
Kernel memory cannot be set in these circumstances (before kernel 4.6):
1. kernel memory is not initialized, and there are tasks in cgroup
2. kernel memory is not initialized, and use_hierarchy is enabled,
and there are sub-cgroups
While we don't need to cover case 2 because when we set kernel
memory in runC, it's either:
- in Apply phase when we create the container, and in this case,
set kernel memory would definitely be valid;
- or in update operation, and in this case, there would be tasks
in cgroup, we only need to check if kernel memory is initialized
or not.
Even if we want to check use_hierarchy, we need to check sub-cgroups
as well, but for here, we can just leave it aside.
Signed-off-by: Qiang Huang <h.huangqiang@huawei.com>
2016-05-28 15:22:58 +08:00
|
|
|
return fmt.Errorf("cannot set kmem.limit_in_bytes after task have joined this cgroup")
|
2016-04-29 01:49:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-13 22:05:08 +08:00
|
|
|
if err := writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemory, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-05-15 06:21:44 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-03-28 10:48:29 +08:00
|
|
|
func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
|
|
|
|
// When memory and swap memory are both set, we need to handle the cases
|
|
|
|
// for updating container.
|
2016-04-12 15:08:10 +08:00
|
|
|
if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap > 0 {
|
2016-03-28 10:48:29 +08:00
|
|
|
memoryUsage, err := getMemoryData(path, "")
|
|
|
|
if err != nil {
|
2015-02-25 17:20:01 +08:00
|
|
|
return err
|
|
|
|
}
|
2016-03-28 10:48:29 +08:00
|
|
|
|
|
|
|
// When update memory limit, we should adapt the write sequence
|
|
|
|
// for memory and swap memory, so it won't fail because the new
|
|
|
|
// value and the old value don't fit kernel's validation.
|
|
|
|
if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
|
|
|
|
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if cgroup.Resources.Memory != 0 {
|
|
|
|
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if cgroup.Resources.MemorySwap > 0 {
|
|
|
|
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-02-25 17:20:01 +08:00
|
|
|
}
|
2016-03-28 10:48:29 +08:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
|
|
|
|
if err := setMemoryAndSwap(path, cgroup); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-29 01:49:29 +08:00
|
|
|
if err := s.SetKernelMemory(path, cgroup); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-12-15 08:26:29 +08:00
|
|
|
if cgroup.Resources.MemoryReservation != 0 {
|
|
|
|
if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
|
2015-02-25 17:20:01 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-03-20 18:45:52 +08:00
|
|
|
if cgroup.Resources.KernelMemoryTCP != 0 {
|
|
|
|
if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2015-12-15 08:26:29 +08:00
|
|
|
if cgroup.Resources.OomKillDisable {
|
2015-03-07 02:37:56 +08:00
|
|
|
if err := writeFile(path, "memory.oom_control", "1"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-02-21 09:29:53 +08:00
|
|
|
if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
|
|
|
|
return nil
|
|
|
|
} else if int64(*cgroup.Resources.MemorySwappiness) >= 0 && int64(*cgroup.Resources.MemorySwappiness) <= 100 {
|
|
|
|
if err := writeFile(path, "memory.swappiness", strconv.FormatInt(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
|
2015-06-11 19:26:03 +08:00
|
|
|
return err
|
|
|
|
}
|
2015-07-03 20:49:45 +08:00
|
|
|
} else {
|
2016-02-21 09:29:53 +08:00
|
|
|
return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", int64(*cgroup.Resources.MemorySwappiness))
|
2015-06-11 19:26:03 +08:00
|
|
|
}
|
2015-03-07 02:37:56 +08:00
|
|
|
|
2015-02-25 17:20:01 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-05 18:41:08 +08:00
|
|
|
func (s *MemoryGroup) Remove(d *cgroupData) error {
|
2014-05-15 06:21:44 +08:00
|
|
|
return removePath(d.path("memory"))
|
|
|
|
}
|
|
|
|
|
2014-06-20 21:13:56 +08:00
|
|
|
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
|
2014-05-15 06:21:44 +08:00
|
|
|
// Set stats from memory.stat.
|
|
|
|
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
|
|
|
|
if err != nil {
|
2014-07-10 00:39:38 +08:00
|
|
|
if os.IsNotExist(err) {
|
2014-07-09 05:25:55 +08:00
|
|
|
return nil
|
|
|
|
}
|
2014-05-28 08:01:08 +08:00
|
|
|
return err
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
|
|
|
defer statsFile.Close()
|
|
|
|
|
|
|
|
sc := bufio.NewScanner(statsFile)
|
|
|
|
for sc.Scan() {
|
|
|
|
t, v, err := getCgroupParamKeyValue(sc.Text())
|
|
|
|
if err != nil {
|
2014-09-24 08:13:57 +08:00
|
|
|
return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2014-05-28 08:01:08 +08:00
|
|
|
stats.MemoryStats.Stats[t] = v
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-05-15 17:37:58 +08:00
|
|
|
stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
|
2014-05-15 06:21:44 +08:00
|
|
|
|
2015-05-15 17:37:58 +08:00
|
|
|
memoryUsage, err := getMemoryData(path, "")
|
2014-05-28 08:01:08 +08:00
|
|
|
if err != nil {
|
2015-05-15 17:37:58 +08:00
|
|
|
return err
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-05-15 17:37:58 +08:00
|
|
|
stats.MemoryStats.Usage = memoryUsage
|
|
|
|
swapUsage, err := getMemoryData(path, "memsw")
|
2014-05-28 08:01:08 +08:00
|
|
|
if err != nil {
|
2015-05-15 17:37:58 +08:00
|
|
|
return err
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-05-15 17:37:58 +08:00
|
|
|
stats.MemoryStats.SwapUsage = swapUsage
|
|
|
|
kernelUsage, err := getMemoryData(path, "kmem")
|
2014-06-04 14:41:03 +08:00
|
|
|
if err != nil {
|
2015-05-15 17:37:58 +08:00
|
|
|
return err
|
2014-06-04 14:41:03 +08:00
|
|
|
}
|
2015-05-15 17:37:58 +08:00
|
|
|
stats.MemoryStats.KernelUsage = kernelUsage
|
2016-03-20 19:04:02 +08:00
|
|
|
kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
|
2014-05-15 06:21:44 +08:00
|
|
|
|
2014-05-28 08:01:08 +08:00
|
|
|
return nil
|
2014-05-15 06:21:44 +08:00
|
|
|
}
|
2015-05-15 17:37:58 +08:00
|
|
|
|
2015-10-20 14:01:48 +08:00
|
|
|
func memoryAssigned(cgroup *configs.Cgroup) bool {
|
2015-12-15 08:26:29 +08:00
|
|
|
return cgroup.Resources.Memory != 0 ||
|
|
|
|
cgroup.Resources.MemoryReservation != 0 ||
|
|
|
|
cgroup.Resources.MemorySwap > 0 ||
|
|
|
|
cgroup.Resources.KernelMemory > 0 ||
|
2016-03-20 18:45:52 +08:00
|
|
|
cgroup.Resources.KernelMemoryTCP > 0 ||
|
2015-12-15 08:26:29 +08:00
|
|
|
cgroup.Resources.OomKillDisable ||
|
2016-02-21 09:29:53 +08:00
|
|
|
(cgroup.Resources.MemorySwappiness != nil && *cgroup.Resources.MemorySwappiness != -1)
|
2015-10-20 14:01:48 +08:00
|
|
|
}
|
|
|
|
|
2015-05-15 17:37:58 +08:00
|
|
|
func getMemoryData(path, name string) (cgroups.MemoryData, error) {
|
|
|
|
memoryData := cgroups.MemoryData{}
|
|
|
|
|
|
|
|
moduleName := "memory"
|
|
|
|
if name != "" {
|
|
|
|
moduleName = strings.Join([]string{"memory", name}, ".")
|
|
|
|
}
|
|
|
|
usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
|
|
|
|
maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
|
|
|
|
failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
|
2016-02-04 03:00:48 +08:00
|
|
|
limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
|
2015-05-15 17:37:58 +08:00
|
|
|
|
|
|
|
value, err := getCgroupParamUint(path, usage)
|
|
|
|
if err != nil {
|
|
|
|
if moduleName != "memory" && os.IsNotExist(err) {
|
|
|
|
return cgroups.MemoryData{}, nil
|
|
|
|
}
|
|
|
|
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
|
|
|
|
}
|
|
|
|
memoryData.Usage = value
|
|
|
|
value, err = getCgroupParamUint(path, maxUsage)
|
|
|
|
if err != nil {
|
|
|
|
if moduleName != "memory" && os.IsNotExist(err) {
|
|
|
|
return cgroups.MemoryData{}, nil
|
|
|
|
}
|
|
|
|
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
|
|
|
|
}
|
|
|
|
memoryData.MaxUsage = value
|
|
|
|
value, err = getCgroupParamUint(path, failcnt)
|
|
|
|
if err != nil {
|
|
|
|
if moduleName != "memory" && os.IsNotExist(err) {
|
|
|
|
return cgroups.MemoryData{}, nil
|
|
|
|
}
|
|
|
|
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
|
|
|
|
}
|
|
|
|
memoryData.Failcnt = value
|
2016-02-04 03:00:48 +08:00
|
|
|
value, err = getCgroupParamUint(path, limit)
|
|
|
|
if err != nil {
|
|
|
|
if moduleName != "memory" && os.IsNotExist(err) {
|
|
|
|
return cgroups.MemoryData{}, nil
|
|
|
|
}
|
|
|
|
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
|
|
|
|
}
|
|
|
|
memoryData.Limit = value
|
2015-05-15 17:37:58 +08:00
|
|
|
|
|
|
|
return memoryData, nil
|
|
|
|
}
|