mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-07-26 19:47:17 +00:00
memory-control-config: add recipe for per-service systemd memory limits
Added a recipe that caps the memory usage of individual systemd
services without modifying those services' own recipes. Services and
limits are given via MEMORY_CONTROL_SERVICES as space-separated
<unit>:<limit>[:<oom_policy>] entries. For each recipe, it generates
a systemd drop-in under ${systemd_system_unitdir} setting MemoryMax,
MemoryHigh (70% of max), and MemorySwapMax=0. The optional "kill" policy
also adds OOMPolicy=continue and OOMScoreAdjust=500.
The service list is empty by default, so the recipe is a no-op until a
distro, machine, or product .bbappend configures it. Drop-ins install to
the system unit directory so systemd reads them at boot with no
daemon-reload. Entries are validated at build time so a bad limit fails
the build.
Signed-off-by: Ishaan Desai <Ishaan.Desai@ibm.com>
Signed-off-by: Khem Raj <khem.raj@oss.qualcomm.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
SUMMARY = "Systemd drop-in memory limits for services"
|
||||||
|
DESCRIPTION = "Installs ${systemd_system_unitdir}/<service>.service.d/memory-control.conf \
|
||||||
|
drop-in files that apply MemoryMax/MemoryHigh cgroup v2 limits to the services \
|
||||||
|
listed in MEMORY_CONTROL_SERVICES, without modifying the service recipes."
|
||||||
|
HOMEPAGE = "https://github.com/ishaandesai23/memory-control-config"
|
||||||
|
SECTION = "base"
|
||||||
|
LICENSE = "Apache-2.0"
|
||||||
|
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
|
||||||
|
|
||||||
|
inherit allarch systemd
|
||||||
|
|
||||||
|
# The drop-ins only mean anything under systemd. OpenBMC selects systemd via
|
||||||
|
# INIT_MANAGER rather than DISTRO_FEATURES, so accept either.
|
||||||
|
python __anonymous() {
|
||||||
|
if (d.getVar('INIT_MANAGER') != 'systemd'
|
||||||
|
and 'systemd' not in (d.getVar('DISTRO_FEATURES') or '').split()):
|
||||||
|
raise bb.parse.SkipRecipe(
|
||||||
|
"memory-control-config requires systemd")
|
||||||
|
}
|
||||||
|
|
||||||
|
RDEPENDS:${PN} = "systemd"
|
||||||
|
|
||||||
|
# Always produce a package, even when no services are configured, so an image
|
||||||
|
# or packagegroup can depend on it unconditionally.
|
||||||
|
ALLOW_EMPTY:${PN} = "1"
|
||||||
|
|
||||||
|
# Nothing is fetched, unpacked, configured or compiled: every file is generated
|
||||||
|
# in do_install from MEMORY_CONTROL_SERVICES.
|
||||||
|
do_configure[noexec] = "1"
|
||||||
|
do_compile[noexec] = "1"
|
||||||
|
do_install[dirs] = "${D}"
|
||||||
|
do_install[vardeps] += "MEMORY_CONTROL_SERVICES"
|
||||||
|
|
||||||
|
|
||||||
|
# Per-service memory limits. <unit>:<limit>[:<oom_policy>]
|
||||||
|
# <unit> systemd service name (the .service suffix is optional)
|
||||||
|
# <limit> raw bytes, or a K/M/G[B] suffix (e.g. 512M, 256K, 1G)
|
||||||
|
# <oom_policy> "default" (bare limits) or "kill" (adds OOMPolicy=continue
|
||||||
|
# and OOMScoreAdjust=500)
|
||||||
|
#
|
||||||
|
# MemoryHigh is 70% of MemoryMax.
|
||||||
|
#
|
||||||
|
# Leave empty here and set from a distro/machine/product .bbappend:
|
||||||
|
# MEMORY_CONTROL_SERVICES:append = " webui:256M:kill inventory-sync:128M"
|
||||||
|
|
||||||
|
MEMORY_CONTROL_SERVICES ??= ""
|
||||||
|
|
||||||
|
|
||||||
|
def mc_parse_services(d):
|
||||||
|
"""Parse MEMORY_CONTROL_SERVICES into a list of
|
||||||
|
(unit, entry, bytes_max, bytes_high, oom_policy) tuples.
|
||||||
|
Calls bb.fatal on any malformed entry so a typo fails the build.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
size_units = {
|
||||||
|
'': 1, 'B': 1,
|
||||||
|
'K': 1024, 'KB': 1024,
|
||||||
|
'M': 1024 ** 2, 'MB': 1024 ** 2,
|
||||||
|
'G': 1024 ** 3, 'GB': 1024 ** 3,
|
||||||
|
}
|
||||||
|
size_re = re.compile(r'^(\d+)([A-Za-z]*)$')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for entry in (d.getVar('MEMORY_CONTROL_SERVICES') or '').split():
|
||||||
|
parts = entry.split(':')
|
||||||
|
if not (2 <= len(parts) <= 3):
|
||||||
|
bb.fatal("memory-control-config: malformed entry '%s'; "
|
||||||
|
"expected <unit>:<limit>[:<oom_policy>]" % entry)
|
||||||
|
|
||||||
|
unit = parts[0].strip()
|
||||||
|
limit_str = parts[1].strip()
|
||||||
|
oom_policy = parts[2].strip().lower() if len(parts) == 3 else 'default'
|
||||||
|
|
||||||
|
if not unit.endswith('.service'):
|
||||||
|
unit += '.service'
|
||||||
|
|
||||||
|
if oom_policy not in ('default', 'kill'):
|
||||||
|
bb.fatal("memory-control-config: OOM policy '%s' for '%s' is "
|
||||||
|
"invalid; use 'default' or 'kill'" % (oom_policy, unit))
|
||||||
|
|
||||||
|
m = size_re.match(limit_str)
|
||||||
|
if not m:
|
||||||
|
bb.fatal("memory-control-config: limit '%s' for '%s' is not a "
|
||||||
|
"valid size (use raw bytes or a K/M/G[B] suffix)"
|
||||||
|
% (limit_str, unit))
|
||||||
|
|
||||||
|
number, suffix = m.group(1), m.group(2).upper()
|
||||||
|
if suffix not in size_units:
|
||||||
|
bb.fatal("memory-control-config: unknown size suffix '%s' in "
|
||||||
|
"'%s' for '%s'" % (m.group(2), limit_str, unit))
|
||||||
|
|
||||||
|
bytes_max = int(number) * size_units[suffix]
|
||||||
|
if bytes_max <= 0:
|
||||||
|
bb.fatal("memory-control-config: limit '%s' for '%s' must be "
|
||||||
|
"greater than zero" % (limit_str, unit))
|
||||||
|
|
||||||
|
bytes_high = bytes_max * 7 // 10
|
||||||
|
result.append((unit, entry, bytes_max, bytes_high, oom_policy))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
python do_install() {
|
||||||
|
import os
|
||||||
|
|
||||||
|
entries = mc_parse_services(d)
|
||||||
|
if not entries:
|
||||||
|
bb.note("memory-control-config: MEMORY_CONTROL_SERVICES is empty; "
|
||||||
|
"nothing to install")
|
||||||
|
return
|
||||||
|
|
||||||
|
destdir = d.getVar('D')
|
||||||
|
unitdir = d.getVar('systemd_system_unitdir')
|
||||||
|
pn = d.getVar('PN')
|
||||||
|
|
||||||
|
for unit, entry, bytes_max, bytes_high, oom_policy in entries:
|
||||||
|
dropin_dir = os.path.join(destdir + unitdir, unit + '.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 %s\n' % pn)
|
||||||
|
f.write('# entry: %s\n' % entry)
|
||||||
|
f.write('[Service]\n')
|
||||||
|
f.write('MemoryMax=%d\n' % bytes_max)
|
||||||
|
f.write('MemoryHigh=%d\n' % bytes_high)
|
||||||
|
f.write('MemorySwapMax=0\n')
|
||||||
|
if oom_policy == 'kill':
|
||||||
|
f.write('OOMPolicy=continue\n')
|
||||||
|
f.write('OOMScoreAdjust=500\n')
|
||||||
|
|
||||||
|
bb.note("memory-control-config: wrote %s (%s, policy=%s)"
|
||||||
|
% (dropin_path, entry, oom_policy))
|
||||||
|
}
|
||||||
|
|
||||||
|
FILES:${PN} += "${systemd_system_unitdir}"
|
||||||
Reference in New Issue
Block a user