* Re: [OE-core] [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-10 17:05 [PATCH] " Ishaan Desai
@ 2026-07-11 18:21 ` Mathieu Dubois-Briand
2026-07-11 20:27 ` Richard Purdie
1 sibling, 0 replies; 10+ messages in thread
From: Mathieu Dubois-Briand @ 2026-07-11 18:21 UTC (permalink / raw)
To: Ishaan.Desai, openembedded-core
On Fri Jul 10, 2026 at 7:05 PM CEST, Ishaan Desai via lists.openembedded.org wrote:
> Adds a CLI tool and bbclass for applying per-service memory limits via
> systemd resource control and Linux cgroups v2.
>
> The memory-control command inspects MemoryMax and MemoryHigh settings
> for any running systemd service and lists all services with a drop-in
> applied.
>
> memory-control.bbclass allows any recipe to set a memory limit at build
> time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
> are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
> and "kill" terminates only the offending process.
>
> AI-Generated: Uses IBM Bob
>
> Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
> ---
Hi Ishaan,
Thanks for your patch.
It looks like this is breaking builds on some platforms:
ERROR: Nothing RPROVIDES 'systemd' (but /srv/pokybuild/yocto-worker/no-x11/build/layers/openembedded-core/meta/recipes-support/memory-control/memory-control_1.0.bb RDEPENDS on or otherwise requires it)
systemd was skipped: using DISTRO 'poky', which is missing required DISTRO_FEATURES: 'usrmerge systemd'
NOTE: Runtime target 'systemd' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['systemd']
NOTE: Runtime target 'memory-control' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['memory-control', 'systemd']
ERROR: Nothing RPROVIDES 'memory-control-dev' (but /srv/pokybuild/yocto-worker/no-x11/build/layers/openembedded-core/meta/recipes-support/memory-control/memory-control_1.0.bb RDEPENDS on or otherwise requires it)
No eligible RPROVIDERs exist for 'memory-control-dev'
NOTE: Runtime target 'memory-control-dev' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['memory-control-dev']
https://autobuilder.yoctoproject.org/valkyrie/#/builders/25/builds/4115
https://autobuilder.yoctoproject.org/valkyrie/#/builders/6/builds/4141
https://autobuilder.yoctoproject.org/valkyrie/api/v2/logs/7643378/raw_inline
Also, please add the corresponding entry in
meta/conf/distro/include/maintainers.inc.
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [OE-core] [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-10 17:05 [PATCH] " Ishaan Desai
2026-07-11 18:21 ` [OE-core] " Mathieu Dubois-Briand
@ 2026-07-11 20:27 ` Richard Purdie
1 sibling, 0 replies; 10+ messages in thread
From: Richard Purdie @ 2026-07-11 20:27 UTC (permalink / raw)
To: Ishaan.Desai, openembedded-core
On Fri, 2026-07-10 at 12:05 -0500, Ishaan Desai via lists.openembedded.org wrote:
> Adds a CLI tool and bbclass for applying per-service memory limits via
> systemd resource control and Linux cgroups v2.
>
> The memory-control command inspects MemoryMax and MemoryHigh settings
> for any running systemd service and lists all services with a drop-in
> applied.
>
> memory-control.bbclass allows any recipe to set a memory limit at build
> time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
> are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
> and "kill" terminates only the offending process.
>
> AI-Generated: Uses IBM Bob
>
> Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
> ---
> meta/classes-recipe/memory-control.bbclass | 95 +++++++++
> .../memory-control/cgroups-v2.cfg | 19 ++
> .../memory-control/memory-control.sh | 196 ++++++++++++++++++
> .../memory-control/memory-control@.service | 13 ++
> .../memory-control/memory-control_1.0.bb | 53 +++++
> 5 files changed, 376 insertions(+)
> create mode 100644 meta/classes-recipe/memory-control.bbclass
> create mode 100644 meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
> create mode 100755 meta/recipes-support/memory-control/memory-control/memory-control.sh
> create mode 100644 meta/recipes-support/memory-control/memory-control/memory-control@.service
> create mode 100644 meta/recipes-support/memory-control/memory-control_1.0.bb
Why a top level bbclass and not just part of the recipe or an .inc
file?
What is the justification for this code in OE-Core? Who is going to use
it and who is going to maintain it?
Cheers,
Richard
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
@ 2026-07-13 18:51 Ishaan Desai
2026-07-14 12:49 ` [OE-core] " Mathieu Dubois-Briand
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Ishaan Desai @ 2026-07-13 18:51 UTC (permalink / raw)
To: openembedded-core; +Cc: Ishaan Desai
Adds a CLI tool and bbclass for applying per-service memory limits via
systemd resource control and Linux cgroups v2.
The memory-control command inspects MemoryMax and MemoryHigh settings
for any running systemd service and lists all services with a drop-in
applied.
memory-control.bbclass allows any recipe to set a memory limit at build
time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
and "kill" terminates only the offending process.
The recipe requires the systemd DISTRO_FEATURE via features_check so it
is cleanly skipped on non-systemd distros. The bbclass guards its
RDEPENDS injection with bb.utils.contains for the same reason.
AI-Generated: Uses IBM Bob
Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
---
meta/classes-recipe/memory-control.bbclass | 97 +++++++++
meta/conf/distro/include/maintainers.inc | 1 +
.../memory-control/cgroups-v2.cfg | 19 ++
.../memory-control/memory-control.sh | 196 ++++++++++++++++++
.../memory-control/memory-control@.service | 13 ++
.../memory-control/memory-control_1.0.bb | 55 +++++
6 files changed, 381 insertions(+)
create mode 100644 meta/classes-recipe/memory-control.bbclass
create mode 100644 meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
create mode 100755 meta/recipes-support/memory-control/memory-control/memory-control.sh
create mode 100644 meta/recipes-support/memory-control/memory-control/memory-control@.service
create mode 100644 meta/recipes-support/memory-control/memory-control_1.0.bb
diff --git a/meta/classes-recipe/memory-control.bbclass b/meta/classes-recipe/memory-control.bbclass
new file mode 100644
index 0000000000..ebde5ed46b
--- /dev/null
+++ b/meta/classes-recipe/memory-control.bbclass
@@ -0,0 +1,97 @@
+# memory-control.bbclass - write a systemd drop-in memory limit into the image
+#
+# Usage:
+# inherit memory-control
+# MEMORY_LIMIT = "512M" # required; K/M/G or raw bytes
+# MEMORY_OOM_POLICY = "kill" # optional; "reboot" (default) or "kill"
+#
+# "reboot": system reboots on OOM (vm.panic_on_oom=2; typical platform default).
+# "kill": only the offending process is killed, system keeps running.
+# Writes /etc/sysctl.d/memory-control.conf with vm.panic_on_oom=1,
+# which sorts after vm.conf and overrides it at boot.
+#
+# MemoryHigh is set to 70% of MemoryMax so the kernel has room to reclaim
+# file-backed pages before hitting the hard ceiling.
+#
+# Kernel config prerequisite:
+# CONFIG_CGROUPS=y and CONFIG_MEMCG=y must be enabled. A reference kernel
+# config fragment is installed to ${datadir}/memory-control/cgroups-v2.cfg.
+# To apply it, copy the fragment to your kernel recipe's files directory and
+# add it to SRC_URI in your kernel bbappend:
+#
+# SRC_URI:append:<machine> = " file://cgroups-v2.cfg"
+
+MEMORY_LIMIT ?= ""
+MEMORY_LIMIT_SERVICE ?= "${PN}.service"
+MEMORY_OOM_POLICY ?= "reboot"
+
+# Only inject the runtime dependency when the distro actually has systemd;
+# memory-control itself requires the systemd DISTRO_FEATURE.
+RDEPENDS:${PN} += "${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'memory-control', '', d)}"
+
+python memory_control_do_install() {
+ import os
+
+ limit_str = d.getVar('MEMORY_LIMIT') or ''
+ if not limit_str:
+ return
+
+ service = d.getVar('MEMORY_LIMIT_SERVICE')
+ destdir = d.getVar('D')
+ oom_policy = (d.getVar('MEMORY_OOM_POLICY') or 'reboot').strip().lower()
+
+ if oom_policy not in ('reboot', 'kill'):
+ bb.fatal("memory-control: MEMORY_OOM_POLICY='%s' is invalid; "
+ "use 'reboot' or 'kill'" % oom_policy)
+
+ limit_str = limit_str.strip()
+ units = {'K': 1024, 'KB': 1024,
+ 'M': 1024**2, 'MB': 1024**2,
+ 'G': 1024**3, 'GB': 1024**3}
+ number_part = ''.join(c for c in limit_str if c.isdigit())
+ unit_part = ''.join(c for c in limit_str if not c.isdigit()).upper()
+
+ if not number_part:
+ bb.fatal("memory-control: MEMORY_LIMIT='%s' is not a valid size" % limit_str)
+
+ bytes_val = int(number_part) * units.get(unit_part, 1)
+ bytes_high = int(bytes_val * 0.7)
+
+ dropin_dir = os.path.join(destdir, 'etc', 'systemd', 'system', service + '.d')
+ os.makedirs(dropin_dir, exist_ok=True)
+
+ dropin_path = os.path.join(dropin_dir, 'memory-control.conf')
+ with open(dropin_path, 'w') as f:
+ f.write('# generated by memory-control.bbclass\n')
+ f.write('# MEMORY_LIMIT = "%s" MEMORY_OOM_POLICY = "%s"\n' % (limit_str, oom_policy))
+ f.write('[Service]\n')
+ f.write('MemoryMax=%d\n' % bytes_val)
+ f.write('MemoryHigh=%d\n' % bytes_high)
+ f.write('MemorySwapMax=0\n')
+ if oom_policy == 'kill':
+ # keep the unit alive after the cgroup OOM kill
+ f.write('OOMPolicy=continue\n')
+ # make this unit the preferred victim for the system-wide OOM killer
+ # so it is killed as a process rather than causing a kernel panic
+ f.write('OOMScoreAdjust=500\n')
+
+ bb.note("memory-control: wrote %s for %s (%s, policy=%s)" % (
+ dropin_path, service, limit_str, oom_policy))
+
+ if oom_policy == 'kill':
+ # override vm.panic_on_oom=2 from vm.conf; value 1 kills only the
+ # process inside the cgroup that hit MemoryMax instead of panicking.
+ sysctl_dir = os.path.join(destdir, 'etc', 'sysctl.d')
+ os.makedirs(sysctl_dir, exist_ok=True)
+ sysctl_path = os.path.join(sysctl_dir, 'memory-control.conf')
+ if not os.path.exists(sysctl_path):
+ with open(sysctl_path, 'w') as f:
+ f.write('# generated by memory-control.bbclass\n')
+ f.write('vm.panic_on_oom=1\n')
+ bb.note("memory-control: wrote %s" % sysctl_path)
+}
+
+do_install[postfuncs] += "memory_control_do_install"
+
+FILES:${PN} += "/etc/systemd/system/${MEMORY_LIMIT_SERVICE}.d/memory-control.conf"
+FILES:${PN} += "${@ '/etc/sysctl.d/memory-control.conf' if (d.getVar('MEMORY_OOM_POLICY') or 'reboot').strip().lower() == 'kill' else '' }"
diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc
index 4c6307086c..8589a67fa4 100644
--- a/meta/conf/distro/include/maintainers.inc
+++ b/meta/conf/distro/include/maintainers.inc
@@ -520,6 +520,7 @@ RECIPE_MAINTAINER:pn-matchbox-wm = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-mc = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-mdadm = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-menu-cache = "Ross Burton <ross.burton@arm.com>"
+RECIPE_MAINTAINER:pn-memory-control = "Ishaan Desai <Ishaan.Desai@ibm.com>"
RECIPE_MAINTAINER:pn-mesa = "Unassigned <unassigned@yoctoproject.org>"
RECIPE_MAINTAINER:pn-mesa-demos = "Unassigned <unassigned@yoctoproject.org>"
RECIPE_MAINTAINER:pn-mesa-gl = "Unassigned <unassigned@yoctoproject.org>"
diff --git a/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg b/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
new file mode 100644
index 0000000000..00661668de
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
@@ -0,0 +1,19 @@
+# kernel config fragment required for cgroup v2 memory accounting
+
+CONFIG_CGROUPS=y
+CONFIG_MEMCG=y
+CONFIG_MEMCG_SWAP=y
+CONFIG_MEMCG_KMEM=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_FAIR_GROUP_SCHED=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_BLK_CGROUP=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=n
+CONFIG_SWAP=y
diff --git a/meta/recipes-support/memory-control/memory-control/memory-control.sh b/meta/recipes-support/memory-control/memory-control/memory-control.sh
new file mode 100755
index 0000000000..251c902ca6
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/memory-control.sh
@@ -0,0 +1,196 @@
+#!/bin/bash
+# memory-control - inspect cgroup v2 memory limits for systemd services
+#
+# requires: kernel CONFIG_MEMCG=y + cgroups v2 (mount | grep cgroup2),
+# systemd 243+, bash 4.0+
+#
+# usage:
+# memory-control get <service> show MemoryMax/MemoryHigh
+# memory-control status <service> show live usage and memory.stat
+# memory-control list list services with a drop-in
+# .service suffix is optional
+#
+# build-time configuration (recipe or .bbappend):
+#
+# inherit memory-control
+# MEMORY_LIMIT = "512M" # K/M/G or raw bytes
+# MEMORY_OOM_POLICY = "kill" # "reboot" (default) or "kill"
+#
+# machine overrides work as normal bitbake syntax:
+# MEMORY_LIMIT:mymachine = "512M"
+#
+# if the unit name differs from <PN>.service:
+# MEMORY_LIMIT_SERVICE = "my-custom-unit.service"
+#
+# accepted sizes: 256K 512M 1G 536870912 (raw bytes)
+#
+# what gets written at build time:
+#
+# /etc/systemd/system/<service>.service.d/memory-control.conf (always)
+# MemoryMax=<bytes> hard limit; cgroup OOM killer fires if exceeded
+# MemoryHigh=<70% of max> reclaim/throttle starts here; 30% gap to max
+# MemorySwapMax=0 swap disabled
+# OOMPolicy=continue kill policy only; unit stays alive after kill
+# OOMScoreAdjust=500 kill policy only; preferred system-wide OOM target
+#
+# /etc/sysctl.d/memory-control.conf (kill policy only)
+# vm.panic_on_oom=1 sorts after vm.conf, overrides =2 at boot
+#
+# oom policy:
+# reboot BMC reboots on OOM; matches vm.panic_on_oom=2 (platform default)
+# kill only the offending process dies; BMC keeps running
+# when MemoryMax is hit:
+# - cgroup OOM killer fires (scoped to this cgroup only),
+# vm.panic_on_oom is not consulted
+# - OOMPolicy=continue keeps the unit alive after the kill
+# - OOMScoreAdjust=500 makes this the preferred target if global
+# RAM is also exhausted, avoiding a kernel panic
+#
+# troubleshooting:
+# cgroups v2 not mounted:
+# mount | grep cgroup2
+# # add systemd.unified_cgroup_hierarchy=1 to kernel cmdline if missing
+#
+# memory controller missing:
+# cat /sys/fs/cgroup/cgroup.controllers # "memory" must appear
+# # rebuild kernel with CONFIG_MEMCG=y if not
+#
+# drop-in not loaded:
+# systemctl cat <service> # check for memory-control.conf
+# systemctl daemon-reload && systemctl restart <service>
+#
+# check kill policy is active:
+# systemctl show <service> -p OOMPolicy -p MemoryMax -p MemoryHigh
+# sysctl vm.panic_on_oom # should be 1
+#
+# MemoryMax shows infinity after daemon-reload:
+# ls /etc/systemd/system/<service>.service.d/
+# ls /usr/lib/systemd/system/<service>.service.d/
+# # look for a conflicting drop-in
+#
+# service keeps getting killed (limit too low):
+# watch memory-control status <service> # find peak usage
+# # set MEMORY_LIMIT to peak + ~30% in the .bbappend and rebuild
+set -e
+
+CGROUP_BASE=/sys/fs/cgroup
+
+usage() {
+ cat <<EOF
+usage: $(basename "$0") <command> [service]
+
+ get <service> show MemoryMax/MemoryHigh
+ status <service> show live usage and memory.stat
+ list list services with a memory-control drop-in
+ -h, --help show this text
+
+Limits are set at build time:
+ inherit memory-control
+ MEMORY_LIMIT = "512M"
+ MEMORY_OOM_POLICY = "kill"
+
+Drop-in location:
+ /etc/systemd/system/<service>.service.d/memory-control.conf
+EOF
+ exit 0
+}
+
+to_human() {
+ local val=$1
+ case "$val" in
+ max|infinity|"") echo "unlimited"; return ;;
+ esac
+ [[ "$val" =~ ^[0-9]+[KMG]$ ]] && { echo "$val"; return; }
+ [[ "$val" =~ ^[0-9]+$ ]] || { echo "unlimited"; return; }
+ if (( val >= 1024**3 )); then echo "$((val / 1024**3))G"
+ elif (( val >= 1024**2 )); then echo "$((val / 1024**2))M"
+ elif (( val >= 1024 )); then echo "$((val / 1024))K"
+ else echo "${val}B"
+ fi
+}
+
+resolve_service() {
+ local input=$1
+ [[ "$input" =~ \.service$ ]] || input="${input}.service"
+
+ local fragment
+ fragment=$(systemctl show "$input" -p FragmentPath --value 2>/dev/null)
+
+ if [[ -z "$fragment" || ! -f "$fragment" ]]; then
+ for dir in /etc/systemd/system /lib/systemd/system /usr/lib/systemd/system; do
+ [[ -f "$dir/$input" ]] && { fragment="$dir/$input"; break; }
+ done
+ fi
+
+ if [[ -z "$fragment" || ! -f "$fragment" ]]; then
+ echo "error: no unit file found for '$input'" >&2
+ return 1
+ fi
+
+ service=$(basename "$fragment")
+}
+
+cmd_get() {
+ local service=$1
+ [[ -n "$service" ]] || { echo "error: service name required" >&2; exit 1; }
+ resolve_service "$service" || exit 1
+
+ echo "service: $service"
+ echo "MemoryMax: $(to_human "$(systemctl show "$service" -p MemoryMax --value)")"
+ echo "MemoryHigh: $(to_human "$(systemctl show "$service" -p MemoryHigh --value)")"
+}
+
+cmd_status() {
+ local service=$1
+ [[ -n "$service" ]] || { echo "error: service name required" >&2; exit 1; }
+ resolve_service "$service" || exit 1
+
+ local state
+ state=$(systemctl is-active "$service" 2>/dev/null || true)
+ echo "service: $service"
+ echo "state: $state"
+
+ if [[ "$state" == "active" ]]; then
+ local mem_max mem_high mem_cur
+ mem_max=$(systemctl show "$service" -p MemoryMax --value)
+ mem_high=$(systemctl show "$service" -p MemoryHigh --value)
+ mem_cur=$(systemctl show "$service" -p MemoryCurrent --value)
+ echo "current: $(to_human "$mem_cur")"
+ echo "high: $(to_human "$mem_high")"
+ echo "max: $(to_human "$mem_max")"
+ [[ "$mem_max" =~ ^[0-9]+$ ]] && echo "usage: $(( mem_cur * 100 / mem_max ))%"
+ fi
+
+ local cgroup_path
+ cgroup_path=$(systemctl show "$service" -p ControlGroup --value)
+ [[ -z "$cgroup_path" ]] && return
+ printf "\ncgroup: %s\n" "$cgroup_path"
+
+ local mem_stat="${CGROUP_BASE}${cgroup_path}/memory.stat"
+ [[ -f "$mem_stat" ]] || return
+ printf "\nmemory.stat:\n"
+ grep -E "^(anon|file|shmem|kernel)" "$mem_stat" | while read -r key val; do
+ printf " %-20s %s\n" "$key" "$(to_human "$val")"
+ done
+}
+
+cmd_list() {
+ local found=0
+ for dir in /etc/systemd/system/*.service.d; do
+ [[ -d "$dir" && -f "$dir/memory-control.conf" ]] || continue
+ local unit
+ unit=$(basename "$dir" .d)
+ printf "%-40s %s\n" "${unit%.service}" \
+ "$(to_human "$(systemctl show "$unit" -p MemoryMax --value 2>/dev/null || echo unknown)")"
+ found=1
+ done
+ (( found )) || echo "no services with memory-control drop-ins found"
+}
+
+case "${1:-}" in
+ get) cmd_get "$2" ;;
+ status) cmd_status "$2" ;;
+ list) cmd_list ;;
+ -h|--help) usage ;;
+ *) usage ;;
+esac
diff --git a/meta/recipes-support/memory-control/memory-control/memory-control@.service b/meta/recipes-support/memory-control/memory-control/memory-control@.service
new file mode 100644
index 0000000000..20efea5a9c
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/memory-control@.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Memory Control for %i
+Documentation=man:systemd.resource-control(5)
+Before=%i.service
+PartOf=%i.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/meta/recipes-support/memory-control/memory-control_1.0.bb b/meta/recipes-support/memory-control/memory-control_1.0.bb
new file mode 100644
index 0000000000..56b156be4b
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control_1.0.bb
@@ -0,0 +1,55 @@
+SUMMARY = "Inspect and apply cgroup v2 memory limits for systemd services"
+DESCRIPTION = "Provides a CLI tool to inspect MemoryMax/MemoryHigh settings \
+for any systemd service, and a bbclass to bake per-service drop-ins into an \
+image at build time."
+HOMEPAGE = "https://www.openembedded.org"
+BUGTRACKER = "https://bugzilla.yoctoproject.org"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+inherit systemd features_check
+
+REQUIRED_DISTRO_FEATURES = "systemd"
+
+SRC_URI = " \
+ file://memory-control.sh \
+ file://memory-control@.service \
+ file://cgroups-v2.cfg \
+"
+
+SYSTEMD_SERVICE:${PN} = ""
+SYSTEMD_AUTO_ENABLE = "disable"
+
+RDEPENDS:${PN} = "bash"
+
+do_install() {
+ install -d ${D}${bindir}
+ install -m 0755 ${UNPACKDIR}/memory-control.sh ${D}${bindir}/memory-control
+
+ install -d ${D}${systemd_system_unitdir}
+ install -m 0644 ${UNPACKDIR}/memory-control@.service \
+ ${D}${systemd_system_unitdir}/
+
+ install -d ${D}${datadir}/memory-control
+ install -m 0644 ${UNPACKDIR}/cgroups-v2.cfg \
+ ${D}${datadir}/memory-control/
+}
+
+pkg_postinst:${PN}() {
+ #!/bin/sh
+ if [ -n "$D" ]; then
+ exit 0
+ fi
+ if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
+ grep -q memory /sys/fs/cgroup/cgroup.controllers || \
+ echo "memory-control: WARNING: memory controller not available in cgroups v2"
+ else
+ echo "memory-control: WARNING: cgroups v2 not detected"
+ fi
+}
+
+FILES:${PN} = " \
+ ${bindir}/memory-control \
+ ${systemd_system_unitdir}/memory-control@.service \
+ ${datadir}/memory-control \
+"
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [OE-core] [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-13 18:51 [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting Ishaan Desai
@ 2026-07-14 12:49 ` Mathieu Dubois-Briand
2026-07-14 13:32 ` Alexander Kanavin
2026-07-14 16:43 ` [PATCH v3] " Ishaan Desai
2 siblings, 0 replies; 10+ messages in thread
From: Mathieu Dubois-Briand @ 2026-07-14 12:49 UTC (permalink / raw)
To: Ishaan.Desai, openembedded-core
On Mon Jul 13, 2026 at 8:51 PM CEST, Ishaan Desai via lists.openembedded.org wrote:
> Adds a CLI tool and bbclass for applying per-service memory limits via
> systemd resource control and Linux cgroups v2.
>
> The memory-control command inspects MemoryMax and MemoryHigh settings
> for any running systemd service and lists all services with a drop-in
> applied.
>
> memory-control.bbclass allows any recipe to set a memory limit at build
> time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
> are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
> and "kill" terminates only the offending process.
>
> The recipe requires the systemd DISTRO_FEATURE via features_check so it
> is cleanly skipped on non-systemd distros. The bbclass guards its
> RDEPENDS injection with bb.utils.contains for the same reason.
>
> AI-Generated: Uses IBM Bob
>
> Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
Hi Ishaan,
Thanks for your patch.
We get a warning about this new recipe during the build:
WARNING: lib32-memory-control-1.0-r0 do_unpack: lib32-memory-control: the directory ${UNPACKDIR}/${BP} (/srv/pokybuild/yocto-worker/qemux86-world-alt/build/build/tmp/work/x86-pokymllib32-linux/lib32-memory-control/1.0/sources/memory-control-1.0) pointed to by the S variable doesn't exist - please set S within the recipe to point to where the source has been unpacked to
https://autobuilder.yoctoproject.org/valkyrie/#/builders/17/builds/3970
https://autobuilder.yoctoproject.org/valkyrie/#/builders/25/builds/4125
Can you make sure everything is correct?
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [OE-core] [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-13 18:51 [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting Ishaan Desai
2026-07-14 12:49 ` [OE-core] " Mathieu Dubois-Briand
@ 2026-07-14 13:32 ` Alexander Kanavin
2026-07-14 16:35 ` Ishaan Desai
2026-07-14 16:43 ` [PATCH v3] " Ishaan Desai
2 siblings, 1 reply; 10+ messages in thread
From: Alexander Kanavin @ 2026-07-14 13:32 UTC (permalink / raw)
To: Ishaan.Desai; +Cc: openembedded-core
On Mon, 13 Jul 2026 at 20:52, Ishaan Desai via lists.openembedded.org
<Ishaan.Desai=ibm.com@lists.openembedded.org> wrote:
> Adds a CLI tool and bbclass for applying per-service memory limits via
> systemd resource control and Linux cgroups v2.
>
> The memory-control command inspects MemoryMax and MemoryHigh settings
> for any running systemd service and lists all services with a drop-in
> applied.
>
> memory-control.bbclass allows any recipe to set a memory limit at build
> time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
> are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
> and "kill" terminates only the offending process.
>
> The recipe requires the systemd DISTRO_FEATURE via features_check so it
> is cleanly skipped on non-systemd distros. The bbclass guards its
> RDEPENDS injection with bb.utils.contains for the same reason.
Note that the question from RP is still standing, and without an
answer, this is not going to be accepted. There's a lot of possible
resource control mechanisms, this doesn't mean oe-core should carry
them.
Should this perhaps be proposed directly to systemd upstream first?
Alex
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-14 13:32 ` Alexander Kanavin
@ 2026-07-14 16:35 ` Ishaan Desai
2026-07-14 16:53 ` [OE-core] " Alexander Kanavin
0 siblings, 1 reply; 10+ messages in thread
From: Ishaan Desai @ 2026-07-14 16:35 UTC (permalink / raw)
To: openembedded-core
[-- Attachment #1: Type: text/plain, Size: 1120 bytes --]
Hi everyone,
The bbclass is intentional. The main use case is for other recipes to inherit it and set a memory limit for their own service at build time. An .inc file would require an explicit require and manual wiring of the do_install postfunc in every recipe that uses the memory-control tool. The bbclass encapsulates that cleanly and follows the OE-Core pattern for cross-recipe functionality. The memory-control recipe itself is just the runtime companion, which the bbclass pulls in as an RDEPEND.
The motivation comes from embedded/constrained platforms, specifically OpenBMC, where per-service memory limits via cgroup v2 are a common requirement. Putting this in OE-Core makes it available to any layer without duplicating the implementation, and the pattern of baking systemd drop-ins at build time is broadly useful beyond any platform.
I'm committed to maintaining this recipe and bbclass going forward, and I've added myself to maintainers.inc. If the consensus is that this is too niche for OE-Core and belongs in a more specific layer, I'm open to that feedback.
Thanks,
Ishaan Desai
**
[-- Attachment #2: Type: text/html, Size: 4941 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-13 18:51 [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting Ishaan Desai
2026-07-14 12:49 ` [OE-core] " Mathieu Dubois-Briand
2026-07-14 13:32 ` Alexander Kanavin
@ 2026-07-14 16:43 ` Ishaan Desai
2026-07-14 16:45 ` Ishaan Desai
2 siblings, 1 reply; 10+ messages in thread
From: Ishaan Desai @ 2026-07-14 16:43 UTC (permalink / raw)
To: openembedded-core; +Cc: Ishaan Desai
Adds a CLI tool and bbclass for applying per-service memory limits via
systemd resource control and Linux cgroups v2.
The memory-control command inspects MemoryMax and MemoryHigh settings
for any running systemd service and lists all services with a drop-in
applied.
memory-control.bbclass allows any recipe to set a memory limit at build
time by inheriting the class and setting MEMORY_LIMIT. Two OOM policies
are available via MEMORY_OOM_POLICY: "reboot" (default) reboots on OOM,
and "kill" terminates only the offending process.
The recipe requires the systemd DISTRO_FEATURE via features_check so it
is cleanly skipped on non-systemd distros. The bbclass guards its
RDEPENDS injection with bb.utils.contains for the same reason.
S is set to ${UNPACKDIR} since all sources are local file:// URIs with
no upstream tarball to unpack into a versioned subdirectory.
AI-Generated: Uses IBM Bob
Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
---
meta/classes-recipe/memory-control.bbclass | 97 +++++++++
meta/conf/distro/include/maintainers.inc | 1 +
.../memory-control/cgroups-v2.cfg | 19 ++
.../memory-control/memory-control.sh | 196 ++++++++++++++++++
.../memory-control/memory-control@.service | 13 ++
.../memory-control/memory-control_1.0.bb | 57 +++++
6 files changed, 383 insertions(+)
create mode 100644 meta/classes-recipe/memory-control.bbclass
create mode 100644 meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
create mode 100755 meta/recipes-support/memory-control/memory-control/memory-control.sh
create mode 100644 meta/recipes-support/memory-control/memory-control/memory-control@.service
create mode 100644 meta/recipes-support/memory-control/memory-control_1.0.bb
diff --git a/meta/classes-recipe/memory-control.bbclass b/meta/classes-recipe/memory-control.bbclass
new file mode 100644
index 0000000000..ebde5ed46b
--- /dev/null
+++ b/meta/classes-recipe/memory-control.bbclass
@@ -0,0 +1,97 @@
+# memory-control.bbclass - write a systemd drop-in memory limit into the image
+#
+# Usage:
+# inherit memory-control
+# MEMORY_LIMIT = "512M" # required; K/M/G or raw bytes
+# MEMORY_OOM_POLICY = "kill" # optional; "reboot" (default) or "kill"
+#
+# "reboot": system reboots on OOM (vm.panic_on_oom=2; typical platform default).
+# "kill": only the offending process is killed, system keeps running.
+# Writes /etc/sysctl.d/memory-control.conf with vm.panic_on_oom=1,
+# which sorts after vm.conf and overrides it at boot.
+#
+# MemoryHigh is set to 70% of MemoryMax so the kernel has room to reclaim
+# file-backed pages before hitting the hard ceiling.
+#
+# Kernel config prerequisite:
+# CONFIG_CGROUPS=y and CONFIG_MEMCG=y must be enabled. A reference kernel
+# config fragment is installed to ${datadir}/memory-control/cgroups-v2.cfg.
+# To apply it, copy the fragment to your kernel recipe's files directory and
+# add it to SRC_URI in your kernel bbappend:
+#
+# SRC_URI:append:<machine> = " file://cgroups-v2.cfg"
+
+MEMORY_LIMIT ?= ""
+MEMORY_LIMIT_SERVICE ?= "${PN}.service"
+MEMORY_OOM_POLICY ?= "reboot"
+
+# Only inject the runtime dependency when the distro actually has systemd;
+# memory-control itself requires the systemd DISTRO_FEATURE.
+RDEPENDS:${PN} += "${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'memory-control', '', d)}"
+
+python memory_control_do_install() {
+ import os
+
+ limit_str = d.getVar('MEMORY_LIMIT') or ''
+ if not limit_str:
+ return
+
+ service = d.getVar('MEMORY_LIMIT_SERVICE')
+ destdir = d.getVar('D')
+ oom_policy = (d.getVar('MEMORY_OOM_POLICY') or 'reboot').strip().lower()
+
+ if oom_policy not in ('reboot', 'kill'):
+ bb.fatal("memory-control: MEMORY_OOM_POLICY='%s' is invalid; "
+ "use 'reboot' or 'kill'" % oom_policy)
+
+ limit_str = limit_str.strip()
+ units = {'K': 1024, 'KB': 1024,
+ 'M': 1024**2, 'MB': 1024**2,
+ 'G': 1024**3, 'GB': 1024**3}
+ number_part = ''.join(c for c in limit_str if c.isdigit())
+ unit_part = ''.join(c for c in limit_str if not c.isdigit()).upper()
+
+ if not number_part:
+ bb.fatal("memory-control: MEMORY_LIMIT='%s' is not a valid size" % limit_str)
+
+ bytes_val = int(number_part) * units.get(unit_part, 1)
+ bytes_high = int(bytes_val * 0.7)
+
+ dropin_dir = os.path.join(destdir, 'etc', 'systemd', 'system', service + '.d')
+ os.makedirs(dropin_dir, exist_ok=True)
+
+ dropin_path = os.path.join(dropin_dir, 'memory-control.conf')
+ with open(dropin_path, 'w') as f:
+ f.write('# generated by memory-control.bbclass\n')
+ f.write('# MEMORY_LIMIT = "%s" MEMORY_OOM_POLICY = "%s"\n' % (limit_str, oom_policy))
+ f.write('[Service]\n')
+ f.write('MemoryMax=%d\n' % bytes_val)
+ f.write('MemoryHigh=%d\n' % bytes_high)
+ f.write('MemorySwapMax=0\n')
+ if oom_policy == 'kill':
+ # keep the unit alive after the cgroup OOM kill
+ f.write('OOMPolicy=continue\n')
+ # make this unit the preferred victim for the system-wide OOM killer
+ # so it is killed as a process rather than causing a kernel panic
+ f.write('OOMScoreAdjust=500\n')
+
+ bb.note("memory-control: wrote %s for %s (%s, policy=%s)" % (
+ dropin_path, service, limit_str, oom_policy))
+
+ if oom_policy == 'kill':
+ # override vm.panic_on_oom=2 from vm.conf; value 1 kills only the
+ # process inside the cgroup that hit MemoryMax instead of panicking.
+ sysctl_dir = os.path.join(destdir, 'etc', 'sysctl.d')
+ os.makedirs(sysctl_dir, exist_ok=True)
+ sysctl_path = os.path.join(sysctl_dir, 'memory-control.conf')
+ if not os.path.exists(sysctl_path):
+ with open(sysctl_path, 'w') as f:
+ f.write('# generated by memory-control.bbclass\n')
+ f.write('vm.panic_on_oom=1\n')
+ bb.note("memory-control: wrote %s" % sysctl_path)
+}
+
+do_install[postfuncs] += "memory_control_do_install"
+
+FILES:${PN} += "/etc/systemd/system/${MEMORY_LIMIT_SERVICE}.d/memory-control.conf"
+FILES:${PN} += "${@ '/etc/sysctl.d/memory-control.conf' if (d.getVar('MEMORY_OOM_POLICY') or 'reboot').strip().lower() == 'kill' else '' }"
diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc
index 4c6307086c..8589a67fa4 100644
--- a/meta/conf/distro/include/maintainers.inc
+++ b/meta/conf/distro/include/maintainers.inc
@@ -520,6 +520,7 @@ RECIPE_MAINTAINER:pn-matchbox-wm = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-mc = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-mdadm = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-menu-cache = "Ross Burton <ross.burton@arm.com>"
+RECIPE_MAINTAINER:pn-memory-control = "Ishaan Desai <Ishaan.Desai@ibm.com>"
RECIPE_MAINTAINER:pn-mesa = "Unassigned <unassigned@yoctoproject.org>"
RECIPE_MAINTAINER:pn-mesa-demos = "Unassigned <unassigned@yoctoproject.org>"
RECIPE_MAINTAINER:pn-mesa-gl = "Unassigned <unassigned@yoctoproject.org>"
diff --git a/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg b/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
new file mode 100644
index 0000000000..00661668de
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/cgroups-v2.cfg
@@ -0,0 +1,19 @@
+# kernel config fragment required for cgroup v2 memory accounting
+
+CONFIG_CGROUPS=y
+CONFIG_MEMCG=y
+CONFIG_MEMCG_SWAP=y
+CONFIG_MEMCG_KMEM=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_FAIR_GROUP_SCHED=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_BLK_CGROUP=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=n
+CONFIG_SWAP=y
diff --git a/meta/recipes-support/memory-control/memory-control/memory-control.sh b/meta/recipes-support/memory-control/memory-control/memory-control.sh
new file mode 100755
index 0000000000..251c902ca6
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/memory-control.sh
@@ -0,0 +1,196 @@
+#!/bin/bash
+# memory-control - inspect cgroup v2 memory limits for systemd services
+#
+# requires: kernel CONFIG_MEMCG=y + cgroups v2 (mount | grep cgroup2),
+# systemd 243+, bash 4.0+
+#
+# usage:
+# memory-control get <service> show MemoryMax/MemoryHigh
+# memory-control status <service> show live usage and memory.stat
+# memory-control list list services with a drop-in
+# .service suffix is optional
+#
+# build-time configuration (recipe or .bbappend):
+#
+# inherit memory-control
+# MEMORY_LIMIT = "512M" # K/M/G or raw bytes
+# MEMORY_OOM_POLICY = "kill" # "reboot" (default) or "kill"
+#
+# machine overrides work as normal bitbake syntax:
+# MEMORY_LIMIT:mymachine = "512M"
+#
+# if the unit name differs from <PN>.service:
+# MEMORY_LIMIT_SERVICE = "my-custom-unit.service"
+#
+# accepted sizes: 256K 512M 1G 536870912 (raw bytes)
+#
+# what gets written at build time:
+#
+# /etc/systemd/system/<service>.service.d/memory-control.conf (always)
+# MemoryMax=<bytes> hard limit; cgroup OOM killer fires if exceeded
+# MemoryHigh=<70% of max> reclaim/throttle starts here; 30% gap to max
+# MemorySwapMax=0 swap disabled
+# OOMPolicy=continue kill policy only; unit stays alive after kill
+# OOMScoreAdjust=500 kill policy only; preferred system-wide OOM target
+#
+# /etc/sysctl.d/memory-control.conf (kill policy only)
+# vm.panic_on_oom=1 sorts after vm.conf, overrides =2 at boot
+#
+# oom policy:
+# reboot BMC reboots on OOM; matches vm.panic_on_oom=2 (platform default)
+# kill only the offending process dies; BMC keeps running
+# when MemoryMax is hit:
+# - cgroup OOM killer fires (scoped to this cgroup only),
+# vm.panic_on_oom is not consulted
+# - OOMPolicy=continue keeps the unit alive after the kill
+# - OOMScoreAdjust=500 makes this the preferred target if global
+# RAM is also exhausted, avoiding a kernel panic
+#
+# troubleshooting:
+# cgroups v2 not mounted:
+# mount | grep cgroup2
+# # add systemd.unified_cgroup_hierarchy=1 to kernel cmdline if missing
+#
+# memory controller missing:
+# cat /sys/fs/cgroup/cgroup.controllers # "memory" must appear
+# # rebuild kernel with CONFIG_MEMCG=y if not
+#
+# drop-in not loaded:
+# systemctl cat <service> # check for memory-control.conf
+# systemctl daemon-reload && systemctl restart <service>
+#
+# check kill policy is active:
+# systemctl show <service> -p OOMPolicy -p MemoryMax -p MemoryHigh
+# sysctl vm.panic_on_oom # should be 1
+#
+# MemoryMax shows infinity after daemon-reload:
+# ls /etc/systemd/system/<service>.service.d/
+# ls /usr/lib/systemd/system/<service>.service.d/
+# # look for a conflicting drop-in
+#
+# service keeps getting killed (limit too low):
+# watch memory-control status <service> # find peak usage
+# # set MEMORY_LIMIT to peak + ~30% in the .bbappend and rebuild
+set -e
+
+CGROUP_BASE=/sys/fs/cgroup
+
+usage() {
+ cat <<EOF
+usage: $(basename "$0") <command> [service]
+
+ get <service> show MemoryMax/MemoryHigh
+ status <service> show live usage and memory.stat
+ list list services with a memory-control drop-in
+ -h, --help show this text
+
+Limits are set at build time:
+ inherit memory-control
+ MEMORY_LIMIT = "512M"
+ MEMORY_OOM_POLICY = "kill"
+
+Drop-in location:
+ /etc/systemd/system/<service>.service.d/memory-control.conf
+EOF
+ exit 0
+}
+
+to_human() {
+ local val=$1
+ case "$val" in
+ max|infinity|"") echo "unlimited"; return ;;
+ esac
+ [[ "$val" =~ ^[0-9]+[KMG]$ ]] && { echo "$val"; return; }
+ [[ "$val" =~ ^[0-9]+$ ]] || { echo "unlimited"; return; }
+ if (( val >= 1024**3 )); then echo "$((val / 1024**3))G"
+ elif (( val >= 1024**2 )); then echo "$((val / 1024**2))M"
+ elif (( val >= 1024 )); then echo "$((val / 1024))K"
+ else echo "${val}B"
+ fi
+}
+
+resolve_service() {
+ local input=$1
+ [[ "$input" =~ \.service$ ]] || input="${input}.service"
+
+ local fragment
+ fragment=$(systemctl show "$input" -p FragmentPath --value 2>/dev/null)
+
+ if [[ -z "$fragment" || ! -f "$fragment" ]]; then
+ for dir in /etc/systemd/system /lib/systemd/system /usr/lib/systemd/system; do
+ [[ -f "$dir/$input" ]] && { fragment="$dir/$input"; break; }
+ done
+ fi
+
+ if [[ -z "$fragment" || ! -f "$fragment" ]]; then
+ echo "error: no unit file found for '$input'" >&2
+ return 1
+ fi
+
+ service=$(basename "$fragment")
+}
+
+cmd_get() {
+ local service=$1
+ [[ -n "$service" ]] || { echo "error: service name required" >&2; exit 1; }
+ resolve_service "$service" || exit 1
+
+ echo "service: $service"
+ echo "MemoryMax: $(to_human "$(systemctl show "$service" -p MemoryMax --value)")"
+ echo "MemoryHigh: $(to_human "$(systemctl show "$service" -p MemoryHigh --value)")"
+}
+
+cmd_status() {
+ local service=$1
+ [[ -n "$service" ]] || { echo "error: service name required" >&2; exit 1; }
+ resolve_service "$service" || exit 1
+
+ local state
+ state=$(systemctl is-active "$service" 2>/dev/null || true)
+ echo "service: $service"
+ echo "state: $state"
+
+ if [[ "$state" == "active" ]]; then
+ local mem_max mem_high mem_cur
+ mem_max=$(systemctl show "$service" -p MemoryMax --value)
+ mem_high=$(systemctl show "$service" -p MemoryHigh --value)
+ mem_cur=$(systemctl show "$service" -p MemoryCurrent --value)
+ echo "current: $(to_human "$mem_cur")"
+ echo "high: $(to_human "$mem_high")"
+ echo "max: $(to_human "$mem_max")"
+ [[ "$mem_max" =~ ^[0-9]+$ ]] && echo "usage: $(( mem_cur * 100 / mem_max ))%"
+ fi
+
+ local cgroup_path
+ cgroup_path=$(systemctl show "$service" -p ControlGroup --value)
+ [[ -z "$cgroup_path" ]] && return
+ printf "\ncgroup: %s\n" "$cgroup_path"
+
+ local mem_stat="${CGROUP_BASE}${cgroup_path}/memory.stat"
+ [[ -f "$mem_stat" ]] || return
+ printf "\nmemory.stat:\n"
+ grep -E "^(anon|file|shmem|kernel)" "$mem_stat" | while read -r key val; do
+ printf " %-20s %s\n" "$key" "$(to_human "$val")"
+ done
+}
+
+cmd_list() {
+ local found=0
+ for dir in /etc/systemd/system/*.service.d; do
+ [[ -d "$dir" && -f "$dir/memory-control.conf" ]] || continue
+ local unit
+ unit=$(basename "$dir" .d)
+ printf "%-40s %s\n" "${unit%.service}" \
+ "$(to_human "$(systemctl show "$unit" -p MemoryMax --value 2>/dev/null || echo unknown)")"
+ found=1
+ done
+ (( found )) || echo "no services with memory-control drop-ins found"
+}
+
+case "${1:-}" in
+ get) cmd_get "$2" ;;
+ status) cmd_status "$2" ;;
+ list) cmd_list ;;
+ -h|--help) usage ;;
+ *) usage ;;
+esac
diff --git a/meta/recipes-support/memory-control/memory-control/memory-control@.service b/meta/recipes-support/memory-control/memory-control/memory-control@.service
new file mode 100644
index 0000000000..20efea5a9c
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control/memory-control@.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Memory Control for %i
+Documentation=man:systemd.resource-control(5)
+Before=%i.service
+PartOf=%i.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/meta/recipes-support/memory-control/memory-control_1.0.bb b/meta/recipes-support/memory-control/memory-control_1.0.bb
new file mode 100644
index 0000000000..1b196e1b85
--- /dev/null
+++ b/meta/recipes-support/memory-control/memory-control_1.0.bb
@@ -0,0 +1,57 @@
+SUMMARY = "Inspect and apply cgroup v2 memory limits for systemd services"
+DESCRIPTION = "Provides a CLI tool to inspect MemoryMax/MemoryHigh settings \
+for any systemd service, and a bbclass to bake per-service drop-ins into an \
+image at build time."
+HOMEPAGE = "https://www.openembedded.org"
+BUGTRACKER = "https://bugzilla.yoctoproject.org"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+inherit systemd features_check
+
+REQUIRED_DISTRO_FEATURES = "systemd"
+
+SRC_URI = " \
+ file://memory-control.sh \
+ file://memory-control@.service \
+ file://cgroups-v2.cfg \
+"
+
+S = "${UNPACKDIR}"
+
+SYSTEMD_SERVICE:${PN} = ""
+SYSTEMD_AUTO_ENABLE = "disable"
+
+RDEPENDS:${PN} = "bash"
+
+do_install() {
+ install -d ${D}${bindir}
+ install -m 0755 ${UNPACKDIR}/memory-control.sh ${D}${bindir}/memory-control
+
+ install -d ${D}${systemd_system_unitdir}
+ install -m 0644 ${UNPACKDIR}/memory-control@.service \
+ ${D}${systemd_system_unitdir}/
+
+ install -d ${D}${datadir}/memory-control
+ install -m 0644 ${UNPACKDIR}/cgroups-v2.cfg \
+ ${D}${datadir}/memory-control/
+}
+
+pkg_postinst:${PN}() {
+ #!/bin/sh
+ if [ -n "$D" ]; then
+ exit 0
+ fi
+ if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
+ grep -q memory /sys/fs/cgroup/cgroup.controllers || \
+ echo "memory-control: WARNING: memory controller not available in cgroups v2"
+ else
+ echo "memory-control: WARNING: cgroups v2 not detected"
+ fi
+}
+
+FILES:${PN} = " \
+ ${bindir}/memory-control \
+ ${systemd_system_unitdir}/memory-control@.service \
+ ${datadir}/memory-control \
+"
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-14 16:43 ` [PATCH v3] " Ishaan Desai
@ 2026-07-14 16:45 ` Ishaan Desai
0 siblings, 0 replies; 10+ messages in thread
From: Ishaan Desai @ 2026-07-14 16:45 UTC (permalink / raw)
To: openembedded-core
[-- Attachment #1: Type: text/plain, Size: 54 bytes --]
Hi everyone,
Sorry about this. Ignore this patch.
[-- Attachment #2: Type: text/html, Size: 91 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [OE-core] [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-14 16:35 ` Ishaan Desai
@ 2026-07-14 16:53 ` Alexander Kanavin
2026-07-14 18:33 ` Ishaan Desai
0 siblings, 1 reply; 10+ messages in thread
From: Alexander Kanavin @ 2026-07-14 16:53 UTC (permalink / raw)
To: Ishaan.Desai; +Cc: openembedded-core
On Tue, 14 Jul 2026 at 18:35, Ishaan Desai via lists.openembedded.org
<Ishaan.Desai=ibm.com@lists.openembedded.org> wrote:
> I'm committed to maintaining this recipe and bbclass going forward, and I've added myself to maintainers.inc. If the consensus is that this is too niche for OE-Core and belongs in a more specific layer, I'm open to that feedback.
I think this should go into a separate layer for now, perhaps a small
layer made specifically for that purpose. You also need to develop
tests and examples.
FWIW, I don't think the bbclass is the best approach. I would make a
special memory-control-config recipe that installs the needed systemd
configurations without touching any of the original recipes that
provide the services.
The control script is written in bash (which carries a problematic
gpl3 license and isn't a great choice for maintainability), I would
rather add needed tweaks to existing systemd tooling.
Alex
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting
2026-07-14 16:53 ` [OE-core] " Alexander Kanavin
@ 2026-07-14 18:33 ` Ishaan Desai
0 siblings, 0 replies; 10+ messages in thread
From: Ishaan Desai @ 2026-07-14 18:33 UTC (permalink / raw)
To: openembedded-core
[-- Attachment #1: Type: text/plain, Size: 781 bytes --]
Hi Alexander,
Thanks for the feedback. I'll move this out of OE-Core. Is there an existing small layer this would fit into, or would you expect this to warrant a new dedicated layer? I'll also add tests and usage examples before resubmitting. I'll drop the bbclass and implement a memory-control-config recipe that installs the needed systemd configuration without touching the recipes that provide the actual services.
I hadn't fully considered the licensing implications there. I'll rewrite the control logic to avoid a bash dependency by extending the existing systemd tooling directly rather than shipping a separate CLI. I'll put together a revised version along these lines and send it out for another look. Thanks again for the advice.
Thanks,
Ishaan Desai
**
[-- Attachment #2: Type: text/html, Size: 4941 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-14 18:33 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13 18:51 [PATCH] memory-control: add recipe and bbclass for cgroup v2 memory limiting Ishaan Desai
2026-07-14 12:49 ` [OE-core] " Mathieu Dubois-Briand
2026-07-14 13:32 ` Alexander Kanavin
2026-07-14 16:35 ` Ishaan Desai
2026-07-14 16:53 ` [OE-core] " Alexander Kanavin
2026-07-14 18:33 ` Ishaan Desai
2026-07-14 16:43 ` [PATCH v3] " Ishaan Desai
2026-07-14 16:45 ` Ishaan Desai
-- strict thread matches above, loose matches on Subject: below --
2026-07-10 17:05 [PATCH] " Ishaan Desai
2026-07-11 18:21 ` [OE-core] " Mathieu Dubois-Briand
2026-07-11 20:27 ` Richard Purdie
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.