From patchwork Tue Jul 14 16:43:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ishaan Desai X-Patchwork-Id: 92420 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id D3A41C44508 for ; Tue, 14 Jul 2026 16:44:17 +0000 (UTC) Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.977.1784047449346592484 for ; Tue, 14 Jul 2026 09:44:09 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@ibm.com header.s=pp1 header.b=Qq2+Stpu; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: ibm.com, ip: 148.163.158.5, mailfrom: ishaan.desai@ibm.com) Received: from pps.filterd (m0356516.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 66EGC6nH1726731 for ; Tue, 14 Jul 2026 16:44:08 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=cRRzoLrepK93lU6+I 49kurxCLzoVw2s7tGWGGXjqjDs=; b=Qq2+StpuxJ/GTlEzYYY2qNt1auK5IDMqF A1EzW6NMDgd9QIe7dJROU3uGevkkJXqXeMg66QevBGIMLia1i2lZk+GBjT72dykJ rtjs6WFHW1L/6mJmMhUAozWCk646VpvFYO1GL7/5zkvFn1CGXyl0dN9kN9etRgfj FBf55uXwJlWXqxnqKROhngoTZiMN+/sUJR1YjvNz/uW27eSkA6uIZt0C9pXWRKtb 6aQiUPIQ7VOroUm3MMPkBdh3Bvs6Pf/KIXWNPXYxNhspfMwSJ7xfjQaCzB+Ac6VH wzugiUqzgPFeOSzCCoE58ob4Qi9jFMSVy1UK/Owd+LE3cM164o29g== Received: from ppma22.wdc07v.mail.ibm.com (5c.69.3da9.ip4.static.sl-reverse.com [169.61.105.92]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4fbf2a6ra5-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT) for ; Tue, 14 Jul 2026 16:44:07 +0000 (GMT) Received: from pps.filterd (ppma22.wdc07v.mail.ibm.com [127.0.0.1]) by ppma22.wdc07v.mail.ibm.com (8.18.1.7/8.18.1.7) with ESMTP id 66EGYaxx013511 for ; Tue, 14 Jul 2026 16:44:07 GMT Received: from smtprelay01.wdc07v.mail.ibm.com ([172.16.1.68]) by ppma22.wdc07v.mail.ibm.com (PPS) with ESMTPS id 4fc0hw3v4w-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT) for ; Tue, 14 Jul 2026 16:44:07 +0000 (GMT) Received: from smtpav05.wdc07v.mail.ibm.com (smtpav05.wdc07v.mail.ibm.com [10.39.53.232]) by smtprelay01.wdc07v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 66EGi5Nk6292380 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 14 Jul 2026 16:44:05 GMT Received: from smtpav05.wdc07v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id ED94D5805D; Tue, 14 Jul 2026 16:44:04 +0000 (GMT) Received: from smtpav05.wdc07v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 9721658053; Tue, 14 Jul 2026 16:44:04 +0000 (GMT) Received: from pfw153.pfw.tadn.ibm.com (unknown [9.5.7.52]) by smtpav05.wdc07v.mail.ibm.com (Postfix) with ESMTP; Tue, 14 Jul 2026 16:44:04 +0000 (GMT) From: Ishaan Desai To: openembedded-core@lists.openembedded.org Cc: Ishaan Desai Subject: [PATCH v3] memory-control: add recipe and bbclass for cgroup v2 memory limiting Date: Tue, 14 Jul 2026 11:43:56 -0500 Message-ID: <20260714164356.1371935-1-Ishaan.Desai@ibm.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260713185154.4171585-1-Ishaan.Desai@ibm.com> References: <20260713185154.4171585-1-Ishaan.Desai@ibm.com> MIME-Version: 1.0 X-TM-AS-GCONF: 00 X-Proofpoint-Spam-Info: AW1haW4tMjYwNzE0MDE3MyBTYWx0ZWRfX0Y6BLZsTlTnx vOqnT/SJSmeJjnwKtASlD1r52f1ZX0xjyXMoxyTG7kLVhOqeEEV6ibr2sOzUf5o6uGCz7ils+Cr WkJBe+hZrLT0vfX8yUYrtYgk9dkOkQo= X-Proofpoint-GUID: s7CJOY5Wn3l-8cZ1_G5rjTgJwH8AGCOS X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNzE0MDE3MyBTYWx0ZWRfX9HwxfeNbAN0C nOQX6JV66hme4q0CEqxtSaP9bRXpS9ckP+r8HQCYzkM2l5MvobA6EX0zyNrqrYXCHB7uTaM3y/Z Z2R/mMHha5D/RVtPe6CGM9Ec0FeC74kAomeHS2AhceJvvZLxWMV0kN+CoN5QSxASUR70LUzkEZJ vWLXz9YEjnyq2KVkwHDlgtUItZyuoFHbjs4ydQ5EDnsuV0CC1Zt51rXrlJ0ypeDi34uT2C4A40s qqTP64CYVA0TueLCerDcvq3dOu81odBi8arY3jIO7rAx3lzPvle+rC5Fitcxl4KPn3FFvep4Jh5 ejgEMZfcE8BCABL/IP2+VK3ykC5+UYJ4lfDWYtHI1FP+dFQBM1myKScTsKde3NU5W+j+lDmJZjN A/RsBzBRgsHdaUR9R5rpIe6rGbN9POVBkVtYZghyyvjBfI9u+zuRDO10QLO4YrxOBh045gZywyu AVfwpShsizWPhfWcatw== X-Proofpoint-ORIG-GUID: s7CJOY5Wn3l-8cZ1_G5rjTgJwH8AGCOS X-Authority-Analysis: v=2.4 cv=PvajqQM3 c=1 sm=1 tr=0 ts=6a566758 cx=c_pps a=5BHTudwdYE3Te8bg5FgnPg==:117 a=5BHTudwdYE3Te8bg5FgnPg==:17 a=RAioF0-LDSMA:10 a=VkNPw1HP01LnGYTKEx00:22 a=RnoormkPH1_aCDwRdu11:22 a=Y2IxJ9c9Rs8Kov3niI8_:22 a=Q4-j1AaZAAAA:8 a=iGHA9ds3AAAA:8 a=VnNF1IyMAAAA:8 a=7CQSdrXTAAAA:8 a=NlPfdOwJ_zJYpz0BUKoA:9 a=IzD9Hpi7bwxJFrhR:21 a=9H3Qd4_ONW2Ztcrla5EB:22 a=nM-MV4yxpKKO9kiQg6Ot:22 a=a-qgeE7W1pNrGK8U0ZQC:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-07-14_04,2026-07-14_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 malwarescore=0 bulkscore=0 impostorscore=0 adultscore=0 clxscore=1015 lowpriorityscore=0 spamscore=0 suspectscore=0 phishscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2607140173 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 14 Jul 2026 16:44:17 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/240901 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 --- 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: = " 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 " RECIPE_MAINTAINER:pn-mc = "Ross Burton " RECIPE_MAINTAINER:pn-mdadm = "Ross Burton " RECIPE_MAINTAINER:pn-menu-cache = "Ross Burton " +RECIPE_MAINTAINER:pn-memory-control = "Ishaan Desai " RECIPE_MAINTAINER:pn-mesa = "Unassigned " RECIPE_MAINTAINER:pn-mesa-demos = "Unassigned " RECIPE_MAINTAINER:pn-mesa-gl = "Unassigned " 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 show MemoryMax/MemoryHigh +# memory-control status 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 .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.d/memory-control.conf (always) +# MemoryMax= 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 # check for memory-control.conf +# systemctl daemon-reload && systemctl restart +# +# check kill policy is active: +# systemctl show -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.d/ +# ls /usr/lib/systemd/system/.service.d/ +# # look for a conflicting drop-in +# +# service keeps getting killed (limit too low): +# watch memory-control status # find peak usage +# # set MEMORY_LIMIT to peak + ~30% in the .bbappend and rebuild +set -e + +CGROUP_BASE=/sys/fs/cgroup + +usage() { + cat < [service] + + get show MemoryMax/MemoryHigh + status 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.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 \ +"