Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ scenario_exec() { container_probe "$1" -m "$SET_MEMORY_BYTES" --cpus "$QUOTA" --
| `bare` | no limits at all, so every reading should fall back to host values |
| `systemd-own` | deep chain, limits on the process's own cgroup, cpuset delegated |
| `systemd-ancestor` | limit on an ancestor while the leaf carries no memory controller files |
| `systemd-quota-above-host` | a cpu quota larger than the machine has cores, which is a permission, not a resource |
| `systemd-memory-above-host` | the same on the memory axis: a limit larger than the machine has RAM |
| `docker-private` | private cgroupns (docker's default), all three axes at once |
| `docker-memory-only` | memory alone, CPU falls back to host |
| `docker-cpuset-only` | cpuset alone, memory falls back to host |
| `docker-host-ns` | `--cgroupns=host`, where the container sees the full chain instead of its own root |
| `docker-nested-subgroup` | a tighter cgroup made inside the container: two levels, two different limits |
| `docker-cgroup-parent` | limit on a parent cgroup, set outside the container (needs docker's systemd driver) |
| `k8s-limits` | a pod with container limits, as kubelet writes them |
| `k8s-no-limits` | a pod with nothing set, so every reading should fall back to the node's values |
Expand Down
1 change: 1 addition & 0 deletions scenario-helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ have_sudo() { sudo -n true 2>/dev/null; }
have_systemd_user() { systemd-run --user --scope -q true 2>/dev/null; }
cgroup_driver() { "$ENGINE" info -f '{{.CgroupDriver}}' 2>/dev/null; }
ncpus() { nproc 2>/dev/null || getconf _NPROCESSORS_ONLN; }
mem_total_bytes() { awk '/^MemTotal:/ {print $2 * 1024}' /proc/meminfo; }

# Prints the first unmet requirement from $REQUIRES, or nothing when all are met.
unmet_requirement() {
Expand Down
26 changes: 26 additions & 0 deletions scenarios/docker-nested-subgroup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# shellcheck shell=bash
SCENARIO_DESC="a tighter cgroup created inside the container, so two levels carry different limits"
REQUIRES="engine"
INNER_STYLE=container

OUTER_MEMORY_BYTES=$((512 * 1024 * 1024))
SET_MEMORY_BYTES=$((256 * 1024 * 1024))

# The container gets one limit and the probe then puts itself under a tighter one, the shape kubelet produces
# and the only scenario here where two levels hold different limits - so the tightest one has to win.
#
# The dance is the kernel's "no internal processes" rule: a cgroup cannot both hold processes and delegate
# controllers to its children, so the shell first vacates the namespace root, then enables the memory
# controller for children, and only then moves into the tighter cgroup it created.
NESTED_SETUP='
mount -o remount,rw /sys/fs/cgroup 2>/dev/null || true
mkdir -p /sys/fs/cgroup/init /sys/fs/cgroup/inner
echo $$ > /sys/fs/cgroup/init/cgroup.procs
echo +memory > /sys/fs/cgroup/cgroup.subtree_control
echo '"$SET_MEMORY_BYTES"' > /sys/fs/cgroup/inner/memory.max
echo $$ > /sys/fs/cgroup/inner/cgroup.procs
'

scenario_exec() {
container_probe "$NESTED_SETUP $1" --privileged -m "$OUTER_MEMORY_BYTES"
}
16 changes: 16 additions & 0 deletions scenarios/systemd-memory-above-host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# shellcheck shell=bash
SCENARIO_DESC="a memory limit larger than the machine has RAM"
REQUIRES="systemd-user"
INNER_STYLE=local

# The memory twin of systemd-quota-above-host, and the axis where the library already guards itself: a limit at
# or above the host's total is treated as no limit, so the derived figures fall back to host values while the
# sensor still reports the number the file holds. Realistic wherever a container or pod is given a limit larger
# than the machine it landed on.
SET_MEMORY_BYTES=$(($(mem_total_bytes) + 1024 * 1024 * 1024))

UNIT="bench-memory-above-host-$$"

scenario_exec() {
systemd-run --user --scope -q --unit "$UNIT" -p "MemoryMax=$SET_MEMORY_BYTES" sh -c "$1"
}
5 changes: 4 additions & 1 deletion scenarios/systemd-own.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ REQUIRES="sudo systemd-run min${BENCH_CPUS}cpu"
INNER_STYLE=local

# A system scope, not a user one: user slices do not delegate cpuset, so AllowedCPUs would be silently ignored.
# The quota is a core looser than the cpuset here, the mirror of docker-private, where it is tighter.
# The quota is a core looser than the cpuset here, the mirror of docker-private, where it is tighter. It stops
# at the machine's core count: a quota above that is a different question, asked on purpose by
# systemd-quota-above-host, and letting this row drift into it would just duplicate that one.
QUOTA_CORES=$((BENCH_CPUS + 1))
[ "$QUOTA_CORES" -gt "$(ncpus)" ] && QUOTA_CORES=$(ncpus)

SET_MEMORY_BYTES=$((512 * 1024 * 1024))
SET_CPU_CORES=$QUOTA_CORES
Expand Down
18 changes: 18 additions & 0 deletions scenarios/systemd-quota-above-host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# shellcheck shell=bash
SCENARIO_DESC="a cpu quota larger than the machine has cores"
REQUIRES="systemd-user"
INNER_STYLE=local

# Nothing rejects a quota above the machine's core count: systemd writes it as asked, and a kubernetes limit
# above node capacity does the same whenever the requests are small enough to schedule. Docker is the exception,
# it validates --cpus against nproc. The cores that exist are the real ceiling, so a derived "allowed cores"
# above them means the utilization ratio is divided by more than can ever be consumed.
QUOTA_CORES=$(($(ncpus) + 1))

SET_CPU_CORES=$QUOTA_CORES

UNIT="bench-quota-above-host-$$"

scenario_exec() {
systemd-run --user --scope -q --unit "$UNIT" -p "CPUQuota=$((QUOTA_CORES * 100))%" sh -c "$1"
}
Loading