mirror of
https://github.com/apple/container.git
synced 2026-09-21 07:05:57 +00:00
Clarify memory units in help and documentation. (#657)
Closes #519. ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Breaking change - [x] Documentation update ## Motivation and Context Clarifies memory units. ## Testing - [x] Tested locally - [x] Added/updated tests - [x] Added/updated docs
This commit is contained in:
@@ -65,7 +65,7 @@ public struct Flags {
|
||||
@Option(
|
||||
name: [.customLong("memory"), .customShort("m")],
|
||||
help:
|
||||
"Amount of memory in bytes, kilobytes (K), megabytes (M), or gigabytes (G) for the container, with MB granularity (for example, 1024K will result in 1MB being allocated for the container)"
|
||||
"Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix"
|
||||
)
|
||||
public var memory: String?
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
private let units: [Character: UnitInformationStorage] = [
|
||||
private let binaryUnits: [Character: UnitInformationStorage] = [
|
||||
"b": .bytes,
|
||||
"k": .kibibytes,
|
||||
"m": .mebibytes,
|
||||
@@ -40,20 +40,22 @@ extension Measurement {
|
||||
}
|
||||
}
|
||||
|
||||
/// parse the provided string into a measurement that is able to be converted to various byte sizes
|
||||
/// parseMemory the provided string into a measurement that is able to be converted to various byte sizes using binary exponents
|
||||
public static func parse(parsing: String) throws -> Measurement<UnitInformationStorage> {
|
||||
let check = "01234567890. "
|
||||
let i = parsing.lastIndex {
|
||||
check.contains($0)
|
||||
}
|
||||
guard let i else {
|
||||
let check = "01234567890."
|
||||
let trimmed = parsing.trimmingCharacters(in: .whitespaces).lowercased()
|
||||
guard !trimmed.isEmpty else {
|
||||
throw ParseError.invalidSize
|
||||
}
|
||||
let after = parsing.index(after: i)
|
||||
let rawValue = parsing[..<after].trimmingCharacters(in: .whitespaces)
|
||||
let rawUnit = parsing[after...]
|
||||
.trimmingCharacters(in: .whitespaces)
|
||||
.lowercased()
|
||||
|
||||
let i = trimmed.firstIndex {
|
||||
!check.contains($0)
|
||||
}
|
||||
let rawValue =
|
||||
i
|
||||
.map { trimmed[..<$0].trimmingCharacters(in: .whitespaces) }
|
||||
?? trimmed
|
||||
let rawUnit = i.map { trimmed[$0...].trimmingCharacters(in: .whitespaces) } ?? ""
|
||||
|
||||
let value = Double(rawValue)
|
||||
guard let value else {
|
||||
@@ -61,7 +63,7 @@ extension Measurement {
|
||||
}
|
||||
let unitSymbol = try Self.parseUnit(rawUnit)
|
||||
|
||||
let unit = units[unitSymbol]
|
||||
let unit = binaryUnits[unitSymbol]
|
||||
guard let unit else {
|
||||
throw ParseError.invalidSymbol(rawUnit)
|
||||
}
|
||||
@@ -70,9 +72,11 @@ extension Measurement {
|
||||
|
||||
static func parseUnit(_ unit: String) throws -> Character {
|
||||
let s = unit.dropFirst()
|
||||
let unitSymbol = unit.first ?? "b"
|
||||
|
||||
switch s {
|
||||
case "", "b", "ib":
|
||||
return unit.first ?? "b"
|
||||
case "", "ib", "b":
|
||||
return unitSymbol
|
||||
default:
|
||||
throw ParseError.invalidSymbol(unit)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ extension Application {
|
||||
@Option(
|
||||
name: [.customLong("memory"), .customShort("m")],
|
||||
help:
|
||||
"Amount of memory in bytes, kilobytes (K), megabytes (M), or gigabytes (G) for the container, with MB granularity (for example, 1024K will result in 1MB being allocated for the container)"
|
||||
"Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix"
|
||||
)
|
||||
var memory: String = "2048MB"
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import ContainerClient
|
||||
|
||||
struct MeasurementParseTests {
|
||||
|
||||
@Test("Parse binary units - bare unit symbols")
|
||||
func testBinaryUnits() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "4k")
|
||||
#expect(result1.value == 4.0)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "2m")
|
||||
#expect(result2.value == 2.0)
|
||||
#expect(result2.unit == .mebibytes)
|
||||
|
||||
let result3 = try Measurement<UnitInformationStorage>.parse(parsing: "1g")
|
||||
#expect(result3.value == 1.0)
|
||||
#expect(result3.unit == .gibibytes)
|
||||
|
||||
let result4 = try Measurement<UnitInformationStorage>.parse(parsing: "512b")
|
||||
#expect(result4.value == 512.0)
|
||||
#expect(result4.unit == .bytes)
|
||||
}
|
||||
|
||||
@Test("Parse binary units - ib suffix")
|
||||
func testBinaryUnitsWithIbSuffix() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "4kib")
|
||||
#expect(result1.value == 4.0)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "2mib")
|
||||
#expect(result2.value == 2.0)
|
||||
#expect(result2.unit == .mebibytes)
|
||||
|
||||
let result3 = try Measurement<UnitInformationStorage>.parse(parsing: "1gib")
|
||||
#expect(result3.value == 1.0)
|
||||
#expect(result3.unit == .gibibytes)
|
||||
|
||||
let result4 = try Measurement<UnitInformationStorage>.parse(parsing: "3tib")
|
||||
#expect(result4.value == 3.0)
|
||||
#expect(result4.unit == .tebibytes)
|
||||
|
||||
let result5 = try Measurement<UnitInformationStorage>.parse(parsing: "1pib")
|
||||
#expect(result5.value == 1.0)
|
||||
#expect(result5.unit == .pebibytes)
|
||||
}
|
||||
|
||||
@Test("Parse binary units - all suffixes now use binary")
|
||||
func testAllSuffixesUseBinary() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "4kb")
|
||||
#expect(result1.value == 4.0)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "2mb")
|
||||
#expect(result2.value == 2.0)
|
||||
#expect(result2.unit == .mebibytes)
|
||||
|
||||
let result3 = try Measurement<UnitInformationStorage>.parse(parsing: "1gb")
|
||||
#expect(result3.value == 1.0)
|
||||
#expect(result3.unit == .gibibytes)
|
||||
|
||||
let result4 = try Measurement<UnitInformationStorage>.parse(parsing: "3tb")
|
||||
#expect(result4.value == 3.0)
|
||||
#expect(result4.unit == .tebibytes)
|
||||
|
||||
let result5 = try Measurement<UnitInformationStorage>.parse(parsing: "1pb")
|
||||
#expect(result5.value == 1.0)
|
||||
#expect(result5.unit == .pebibytes)
|
||||
}
|
||||
|
||||
@Test("Parse with whitespace")
|
||||
func testParsingWithWhitespace() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: " 4k ")
|
||||
#expect(result1.value == 4.0)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: " 2.5mb ")
|
||||
#expect(result2.value == 2.5)
|
||||
#expect(result2.unit == .mebibytes)
|
||||
}
|
||||
|
||||
@Test("Parse decimal values")
|
||||
func testDecimalValues() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "4.5k")
|
||||
#expect(result1.value == 4.5)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "1.25gb")
|
||||
#expect(result2.value == 1.25)
|
||||
#expect(result2.unit == .gibibytes)
|
||||
|
||||
let result3 = try Measurement<UnitInformationStorage>.parse(parsing: "0.5mib")
|
||||
#expect(result3.value == 0.5)
|
||||
#expect(result3.unit == .mebibytes)
|
||||
}
|
||||
|
||||
@Test("Parse case insensitive")
|
||||
func testCaseInsensitive() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "4K")
|
||||
#expect(result1.value == 4.0)
|
||||
#expect(result1.unit == .kibibytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "2GB")
|
||||
#expect(result2.value == 2.0)
|
||||
#expect(result2.unit == .gibibytes)
|
||||
|
||||
let result3 = try Measurement<UnitInformationStorage>.parse(parsing: "1MIB")
|
||||
#expect(result3.value == 1.0)
|
||||
#expect(result3.unit == .mebibytes)
|
||||
}
|
||||
|
||||
@Test("Parse bytes unit")
|
||||
func testBytesUnit() throws {
|
||||
let result1 = try Measurement<UnitInformationStorage>.parse(parsing: "1024")
|
||||
#expect(result1.value == 1024.0)
|
||||
#expect(result1.unit == .bytes)
|
||||
|
||||
let result2 = try Measurement<UnitInformationStorage>.parse(parsing: "512b")
|
||||
#expect(result2.value == 512.0)
|
||||
#expect(result2.unit == .bytes)
|
||||
}
|
||||
|
||||
@Test("Parse invalid size throws error")
|
||||
func testInvalidSizeThrowsError() {
|
||||
#expect {
|
||||
_ = try Measurement<UnitInformationStorage>.parse(parsing: "abc")
|
||||
} throws: { error in
|
||||
guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {
|
||||
return false
|
||||
}
|
||||
return parseError.description == "invalid size"
|
||||
}
|
||||
|
||||
#expect {
|
||||
_ = try Measurement<UnitInformationStorage>.parse(parsing: "k4")
|
||||
} throws: { error in
|
||||
guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {
|
||||
return false
|
||||
}
|
||||
return parseError.description == "invalid size"
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Parse invalid symbol throws error")
|
||||
func testInvalidSymbolThrowsError() {
|
||||
#expect {
|
||||
_ = try Measurement<UnitInformationStorage>.parse(parsing: "4x")
|
||||
} throws: { error in
|
||||
guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {
|
||||
return false
|
||||
}
|
||||
return parseError.description == "invalid symbol: x"
|
||||
}
|
||||
|
||||
#expect {
|
||||
_ = try Measurement<UnitInformationStorage>.parse(parsing: "4kx")
|
||||
} throws: { error in
|
||||
guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {
|
||||
return false
|
||||
}
|
||||
return parseError.description == "invalid symbol: kx"
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Parse empty string throws error")
|
||||
func testEmptyStringThrowsError() {
|
||||
#expect {
|
||||
_ = try Measurement<UnitInformationStorage>.parse(parsing: "")
|
||||
} throws: { error in
|
||||
guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {
|
||||
return false
|
||||
}
|
||||
return parseError.description == "invalid size"
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Verify all suffixes now use binary units")
|
||||
func testAllSuffixesUseBinaryUnits() throws {
|
||||
let bareK = try Measurement<UnitInformationStorage>.parse(parsing: "1k")
|
||||
let kib = try Measurement<UnitInformationStorage>.parse(parsing: "1kib")
|
||||
let kb = try Measurement<UnitInformationStorage>.parse(parsing: "1kb")
|
||||
|
||||
#expect(bareK.unit == .kibibytes)
|
||||
#expect(kib.unit == .kibibytes)
|
||||
#expect(kb.unit == .kibibytes)
|
||||
|
||||
let allInBytes = bareK.converted(to: .bytes).value
|
||||
|
||||
#expect(allInBytes == 1024.0)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,7 +4,7 @@ How to use the features of `container`.
|
||||
|
||||
## Configure memory and CPUs for your containers
|
||||
|
||||
Since the containers created by `container` are lightweight virtual machines, consider the needs of your containerized application when you use `container run`. The `--memory` and `--cpus` options allow you to override the default memory and CPU limits for the virtual machine. The default values are 1 gigabyte of RAM and 4 CPUs. You can use abbreviations for memory units; for example, to run a container for image `big` with 8 CPUs and 32 gigabytes of memory, use:
|
||||
Since the containers created by `container` are lightweight virtual machines, consider the needs of your containerized application when you use `container run`. The `--memory` and `--cpus` options allow you to override the default memory and CPU limits for the virtual machine. The default values are 1 gigabyte of RAM and 4 CPUs. You can use abbreviations for memory units; for example, to run a container for image `big` with 8 CPUs and 32 GiBytes of memory, use:
|
||||
|
||||
```bash
|
||||
container run --rm --cpus 8 --memory 32g big
|
||||
@@ -14,7 +14,7 @@ container run --rm --cpus 8 --memory 32g big
|
||||
|
||||
When you first run `container build`, `container` starts a *builder*, which is a utility container that builds images from your `Dockerfile`s. As with anything you run with `container run`, the builder runs in a lightweight virtual machine, so for resource-intensive builds, you may need to increase the memory and CPU limits for the builder VM.
|
||||
|
||||
By default, the builder VM receives 2 gigabytes of RAM and 2 CPUs. You can change these limits by starting the builder container before running `container build`:
|
||||
By default, the builder VM receives 2 GiBytes of RAM and 2 CPUs. You can change these limits by starting the builder container before running `container build`:
|
||||
|
||||
```bash
|
||||
container builder start --cpus 8 --memory 32g
|
||||
|
||||
@@ -61,7 +61,7 @@ socat TCP-LISTEN:8000,fork,bind=192.168.64.1 TCP:127.0.0.1:8000
|
||||
|
||||
### Releasing container memory to macOS
|
||||
|
||||
The macOS Virtualization framework implements only partial support for memory ballooning, which is a technology that allows virtual machines to dynamically use and relinquish host memory. When you create a container, the underlying virtual machine only uses the amount of memory that the containerized application needs. For example, you might start a container using the option `--memory 16g`, but see that the application is only using 2 gigabytes of RAM in the macOS Activity Monitor.
|
||||
The macOS Virtualization framework implements only partial support for memory ballooning, which is a technology that allows virtual machines to dynamically use and relinquish host memory. When you create a container, the underlying virtual machine only uses the amount of memory that the containerized application needs. For example, you might start a container using the option `--memory 16g`, but see that the application is only using 2 GiBytes of RAM in the macOS Activity Monitor.
|
||||
|
||||
Currently, memory pages freed to the Linux operating system by processes running in the container's VM are not relinquished to the host. If you run many memory-intensive containers, you may need to occasionally restart them to reduce memory utilization.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user