From 9ee9d2f9959390d1cda56accbbd975df18d157ad Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 3 Oct 2013 18:23:29 +0000 Subject: [PATCH] Container memory limit can be specified in kilobytes, megabytes or gigabytes -m 10 # 10 bytes -m 10b # 10 bytes -m 10k # 10240 bytes (10 * 1024) -m 10m # 10485760 bytes (10 * 1024 * 1024) -m 10g # 10737418240 bytes (10 * 1024 * 1024 * 1024) Units are case-insensitive, and 'kb', 'mb' and 'gb' are equivalent to 'k', 'm' and 'g'. --- container.go | 22 ++++++++++++++++----- docs/sources/commandline/cli.rst | 2 +- utils/utils.go | 32 +++++++++++++++++++++++++++++++ utils/utils_test.go | 33 ++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index e67c8f5b0b..30c010a66c 100644 --- a/container.go +++ b/container.go @@ -162,7 +162,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") - flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + flMemoryString := cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") flNetwork := cmd.Bool("n", true, "Enable networking for this container") flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container") @@ -170,9 +170,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") cmd.String("name", "", "Assign a name to the container") - if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { + if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") - *flMemory = 0 + *flMemoryString = "" } flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") @@ -239,6 +239,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } } + var flMemory int64 + + if *flMemoryString != "" { + parsedMemory, err := utils.RAMInBytes(*flMemoryString) + + if err != nil { + return nil, nil, cmd, err + } + + flMemory = parsedMemory + } + var binds []string // add any bind targets to the list of container volumes @@ -306,7 +318,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Tty: *flTty, NetworkDisabled: !*flNetwork, OpenStdin: *flStdin, - Memory: *flMemory, + Memory: flMemory, CpuShares: *flCpuShares, AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), @@ -330,7 +342,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Links: flLinks, } - if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { + if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index c6579da355..a3c2b9eeda 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -568,7 +568,7 @@ network communication. -h="": Container host name -i=false: Keep stdin open even if not attached -privileged=false: Give extended privileges to this container - -m=0: Memory limit (in bytes) + -m="": Memory limit (format: , where unit = b, k, m or g) -n=true: Enable networking for this container -p=[]: Map a network port to the container -rm=false: Automatically remove the container when it exits (incompatible with -d) diff --git a/utils/utils.go b/utils/utils.go index d53094397f..c37b3cc6a1 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -20,6 +20,7 @@ import ( "strings" "sync" "time" + "regexp" ) var ( @@ -176,6 +177,37 @@ func HumanSize(size int64) string { return fmt.Sprintf("%.4g %s", sizef, units[i]) } +// Parses a human-readable string representing an amount of RAM +// in bytes, kibibytes, mebibytes or gibibytes, and returns the +// number of bytes, or -1 if the string is unparseable. +// Units are case-insensitive, and the 'b' suffix is optional. +func RAMInBytes(size string) (bytes int64, err error) { + re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") + if error != nil { return -1, error } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + memLimit, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { return -1, error } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + memLimit *= 1024 + } else if unit == "m" { + memLimit *= 1024*1024 + } else if unit == "g" { + memLimit *= 1024*1024*1024 + } + + return memLimit, nil +} + + func Trunc(s string, maxlen int) string { if len(s) <= maxlen { return s diff --git a/utils/utils_test.go b/utils/utils_test.go index 49f19bf759..8775bd0891 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -265,6 +265,39 @@ func TestHumanSize(t *testing.T) { } } +func TestRAMInBytes(t *testing.T) { + assertRAMInBytes(t, "32", false, 32) + assertRAMInBytes(t, "32b", false, 32) + assertRAMInBytes(t, "32B", false, 32) + assertRAMInBytes(t, "32k", false, 32*1024) + assertRAMInBytes(t, "32K", false, 32*1024) + assertRAMInBytes(t, "32kb", false, 32*1024) + assertRAMInBytes(t, "32Kb", false, 32*1024) + assertRAMInBytes(t, "32Mb", false, 32*1024*1024) + assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024) + + assertRAMInBytes(t, "", true, -1) + assertRAMInBytes(t, "hello", true, -1) + assertRAMInBytes(t, "-32", true, -1) + assertRAMInBytes(t, " 32 ", true, -1) + assertRAMInBytes(t, "32 mb", true, -1) + assertRAMInBytes(t, "32m b", true, -1) + assertRAMInBytes(t, "32bm", true, -1) +} + +func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := RAMInBytes(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} + func TestParseHost(t *testing.T) { if addr, err := ParseHost("127.0.0.1", 4243, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" { t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)