ryantimwilson / rpms / systemd

Forked from rpms/systemd a month ago
Clone
b9a53a
From b55c9b8e717d1967e6aa16c1e2646fc81d899ab7 Mon Sep 17 00:00:00 2001
b9a53a
From: Pavel Hrdina <phrdina@redhat.com>
b9a53a
Date: Mon, 29 Jul 2019 17:50:05 +0200
b9a53a
Subject: [PATCH] cgroup: introduce support for cgroup v2 CPUSET controller
b9a53a
b9a53a
Introduce support for configuring cpus and mems for processes using
b9a53a
cgroup v2 CPUSET controller.  This allows users to limit which cpus
b9a53a
and memory NUMA nodes can be used by processes to better utilize
b9a53a
system resources.
b9a53a
b9a53a
The cgroup v2 interfaces to control it are cpuset.cpus and cpuset.mems
b9a53a
where the requested configuration is written.  However, it doesn't mean
b9a53a
that the requested configuration will be actually used as parent cgroup
b9a53a
may limit the cpus or mems as well.  In order to reflect the real
b9a53a
configuration cgroup v2 provides read-only files cpuset.cpus.effective
b9a53a
and cpuset.mems.effective which are exported to users as well.
b9a53a
b9a53a
(cherry picked from commit 047f5d63d7a1ab75073f8485e2f9b550d25b0772)
b9a53a
b9a53a
Related: #1724617
b9a53a
---
b9a53a
 doc/TRANSIENT-SETTINGS.md             |  2 +
b9a53a
 man/systemd.resource-control.xml      | 30 +++++++++++++
b9a53a
 src/basic/cgroup-util.c               |  1 +
b9a53a
 src/basic/cgroup-util.h               |  2 +
b9a53a
 src/core/cgroup.c                     | 63 +++++++++++++++++++++++++++
b9a53a
 src/core/cgroup.h                     |  5 +++
b9a53a
 src/core/dbus-cgroup.c                | 59 +++++++++++++++++++++++++
b9a53a
 src/core/dbus-unit.c                  | 48 ++++++++++++++++++++
b9a53a
 src/core/load-fragment-gperf.gperf.m4 |  2 +
b9a53a
 src/core/load-fragment.c              | 38 ++++++++++++++++
b9a53a
 src/core/load-fragment.h              |  2 +
b9a53a
 src/shared/bus-unit-util.c            | 16 +++++++
b9a53a
 src/systemctl/systemctl.c             |  2 +-
b9a53a
 src/test/test-cgroup-mask.c           |  3 +-
b9a53a
 14 files changed, 271 insertions(+), 2 deletions(-)
b9a53a
b9a53a
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
b9a53a
index c2b5c0dcce..0b2ad66dcb 100644
b9a53a
--- a/doc/TRANSIENT-SETTINGS.md
b9a53a
+++ b/doc/TRANSIENT-SETTINGS.md
b9a53a
@@ -218,6 +218,8 @@ All cgroup/resource control settings are available for transient units
b9a53a
 ✓ CPUShares=
b9a53a
 ✓ StartupCPUShares=
b9a53a
 ✓ CPUQuota=
b9a53a
+✓ AllowedCPUs=
b9a53a
+✓ AllowedMemoryNodes=
b9a53a
 ✓ MemoryAccounting=
b9a53a
 ✓ MemoryLow=
b9a53a
 ✓ MemoryHigh=
b9a53a
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
b9a53a
index 370c110592..4329742e94 100644
b9a53a
--- a/man/systemd.resource-control.xml
b9a53a
+++ b/man/systemd.resource-control.xml
b9a53a
@@ -201,6 +201,36 @@
b9a53a
         </listitem>
b9a53a
       </varlistentry>
b9a53a
 
b9a53a
+      <varlistentry>
b9a53a
+        <term><varname>AllowedCPUs=</varname></term>
b9a53a
+
b9a53a
+        <listitem>
b9a53a
+          <para>Restrict processes to be executed on specific CPUs. Takes a list of CPU indices or ranges separated by either
b9a53a
+          whitespace or commas. CPU ranges are specified by the lower and upper CPU indices separated by a dash.</para>
b9a53a
+
b9a53a
+          <para>Setting <varname>AllowedCPUs=</varname> doesn't guarantee that all of the CPUs will be used by the processes
b9a53a
+          as it may be limited by parent units. The effective configuration is reported as <varname>EffectiveCPUs=</varname>.</para>
b9a53a
+
b9a53a
+          <para>This setting is supported only with the unified control group hierarchy.</para>
b9a53a
+        </listitem>
b9a53a
+      </varlistentry>
b9a53a
+
b9a53a
+      <varlistentry>
b9a53a
+        <term><varname>AllowedMemoryNodes=</varname></term>
b9a53a
+
b9a53a
+        <listitem>
b9a53a
+          <para>Restrict processes to be executed on specific memory NUMA nodes. Takes a list of memory NUMA nodes indices
b9a53a
+          or ranges separated by either whitespace or commas. Memory NUMA nodes ranges are specified by the lower and upper
b9a53a
+          CPU indices separated by a dash.</para>
b9a53a
+
b9a53a
+          <para>Setting <varname>AllowedMemoryNodes=</varname> doesn't guarantee that all of the memory NUMA nodes will
b9a53a
+          be used by the processes as it may be limited by parent units. The effective configuration is reported as
b9a53a
+          <varname>EffectiveMemoryNodes=</varname>.</para>
b9a53a
+
b9a53a
+          <para>This setting is supported only with the unified control group hierarchy.</para>
b9a53a
+        </listitem>
b9a53a
+      </varlistentry>
b9a53a
+
b9a53a
       <varlistentry>
b9a53a
         <term><varname>MemoryAccounting=</varname></term>
b9a53a
 
b9a53a
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
b9a53a
index 038ece4b06..6f47c3aacb 100644
b9a53a
--- a/src/basic/cgroup-util.c
b9a53a
+++ b/src/basic/cgroup-util.c
b9a53a
@@ -2763,6 +2763,7 @@ bool fd_is_cgroup_fs(int fd) {
b9a53a
 static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
b9a53a
         [CGROUP_CONTROLLER_CPU] = "cpu",
b9a53a
         [CGROUP_CONTROLLER_CPUACCT] = "cpuacct",
b9a53a
+        [CGROUP_CONTROLLER_CPUSET] = "cpuset",
b9a53a
         [CGROUP_CONTROLLER_IO] = "io",
b9a53a
         [CGROUP_CONTROLLER_BLKIO] = "blkio",
b9a53a
         [CGROUP_CONTROLLER_MEMORY] = "memory",
b9a53a
diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
b9a53a
index 26e3ae0404..b414600dca 100644
b9a53a
--- a/src/basic/cgroup-util.h
b9a53a
+++ b/src/basic/cgroup-util.h
b9a53a
@@ -21,6 +21,7 @@
b9a53a
 typedef enum CGroupController {
b9a53a
         CGROUP_CONTROLLER_CPU,
b9a53a
         CGROUP_CONTROLLER_CPUACCT,    /* v1 only */
b9a53a
+        CGROUP_CONTROLLER_CPUSET,     /* v2 only */
b9a53a
         CGROUP_CONTROLLER_IO,         /* v2 only */
b9a53a
         CGROUP_CONTROLLER_BLKIO,      /* v1 only */
b9a53a
         CGROUP_CONTROLLER_MEMORY,
b9a53a
@@ -36,6 +37,7 @@ typedef enum CGroupController {
b9a53a
 typedef enum CGroupMask {
b9a53a
         CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU),
b9a53a
         CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT),
b9a53a
+        CGROUP_MASK_CPUSET = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUSET),
b9a53a
         CGROUP_MASK_IO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_IO),
b9a53a
         CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO),
b9a53a
         CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY),
b9a53a
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
b9a53a
index 76eafdc082..664d269483 100644
b9a53a
--- a/src/core/cgroup.c
b9a53a
+++ b/src/core/cgroup.c
b9a53a
@@ -161,9 +161,14 @@ void cgroup_context_done(CGroupContext *c) {
b9a53a
 
b9a53a
         c->ip_address_allow = ip_address_access_free_all(c->ip_address_allow);
b9a53a
         c->ip_address_deny = ip_address_access_free_all(c->ip_address_deny);
b9a53a
+
b9a53a
+        cpu_set_reset(&c->cpuset_cpus);
b9a53a
+        cpu_set_reset(&c->cpuset_mems);
b9a53a
 }
b9a53a
 
b9a53a
 void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
b9a53a
+        _cleanup_free_ char *cpuset_cpus = NULL;
b9a53a
+        _cleanup_free_ char *cpuset_mems = NULL;
b9a53a
         CGroupIODeviceLimit *il;
b9a53a
         CGroupIODeviceWeight *iw;
b9a53a
         CGroupBlockIODeviceBandwidth *b;
b9a53a
@@ -177,6 +182,9 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
b9a53a
 
b9a53a
         prefix = strempty(prefix);
b9a53a
 
b9a53a
+        cpuset_cpus = cpu_set_to_range_string(&c->cpuset_cpus);
b9a53a
+        cpuset_mems = cpu_set_to_range_string(&c->cpuset_mems);
b9a53a
+
b9a53a
         fprintf(f,
b9a53a
                 "%sCPUAccounting=%s\n"
b9a53a
                 "%sIOAccounting=%s\n"
b9a53a
@@ -189,6 +197,8 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
b9a53a
                 "%sCPUShares=%" PRIu64 "\n"
b9a53a
                 "%sStartupCPUShares=%" PRIu64 "\n"
b9a53a
                 "%sCPUQuotaPerSecSec=%s\n"
b9a53a
+                "%sAllowedCPUs=%s\n"
b9a53a
+                "%sAllowedMemoryNodes=%s\n"
b9a53a
                 "%sIOWeight=%" PRIu64 "\n"
b9a53a
                 "%sStartupIOWeight=%" PRIu64 "\n"
b9a53a
                 "%sBlockIOWeight=%" PRIu64 "\n"
b9a53a
@@ -212,6 +222,8 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
b9a53a
                 prefix, c->cpu_shares,
b9a53a
                 prefix, c->startup_cpu_shares,
b9a53a
                 prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1),
b9a53a
+                prefix, cpuset_cpus,
b9a53a
+                prefix, cpuset_mems,
b9a53a
                 prefix, c->io_weight,
b9a53a
                 prefix, c->startup_io_weight,
b9a53a
                 prefix, c->blockio_weight,
b9a53a
@@ -541,6 +553,21 @@ static uint64_t cgroup_cpu_weight_to_shares(uint64_t weight) {
b9a53a
                      CGROUP_CPU_SHARES_MIN, CGROUP_CPU_SHARES_MAX);
b9a53a
 }
b9a53a
 
b9a53a
+static void cgroup_apply_unified_cpuset(Unit *u, CPUSet cpus, const char *name) {
b9a53a
+        _cleanup_free_ char *buf = NULL;
b9a53a
+        int r;
b9a53a
+
b9a53a
+        buf = cpu_set_to_range_string(&cpus);
b9a53a
+        if (!buf)
b9a53a
+            return;
b9a53a
+
b9a53a
+        r = cg_set_attribute("cpuset", u->cgroup_path, name, buf);
b9a53a
+        if (r < 0)
b9a53a
+                log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
b9a53a
+                              "Failed to set %s: %m", name);
b9a53a
+
b9a53a
+}
b9a53a
+
b9a53a
 static bool cgroup_context_has_io_config(CGroupContext *c) {
b9a53a
         return c->io_accounting ||
b9a53a
                 c->io_weight != CGROUP_WEIGHT_INVALID ||
b9a53a
@@ -766,6 +793,11 @@ static void cgroup_context_apply(
b9a53a
                 }
b9a53a
         }
b9a53a
 
b9a53a
+        if ((apply_mask & CGROUP_MASK_CPUSET) && !is_root) {
b9a53a
+                cgroup_apply_unified_cpuset(u, c->cpuset_cpus, "cpuset.cpus");
b9a53a
+                cgroup_apply_unified_cpuset(u, c->cpuset_mems, "cpuset.mems");
b9a53a
+        }
b9a53a
+
b9a53a
         if (apply_mask & CGROUP_MASK_IO) {
b9a53a
                 bool has_io = cgroup_context_has_io_config(c);
b9a53a
                 bool has_blockio = cgroup_context_has_blockio_config(c);
b9a53a
@@ -1068,6 +1100,9 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c) {
b9a53a
             c->cpu_quota_per_sec_usec != USEC_INFINITY)
b9a53a
                 mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU;
b9a53a
 
b9a53a
+        if (c->cpuset_cpus.set || c->cpuset_mems.set)
b9a53a
+                mask |= CGROUP_MASK_CPUSET;
b9a53a
+
b9a53a
         if (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c))
b9a53a
                 mask |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO;
b9a53a
 
b9a53a
@@ -2697,4 +2732,32 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] =
b9a53a
         [CGROUP_STRICT] = "strict",
b9a53a
 };
b9a53a
 
b9a53a
+int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) {
b9a53a
+        _cleanup_free_ char *v = NULL;
b9a53a
+        int r;
b9a53a
+
b9a53a
+        assert(u);
b9a53a
+        assert(cpus);
b9a53a
+
b9a53a
+        if (!u->cgroup_path)
b9a53a
+                return -ENODATA;
b9a53a
+
b9a53a
+        if ((u->cgroup_realized_mask & CGROUP_MASK_CPUSET) == 0)
b9a53a
+                return -ENODATA;
b9a53a
+
b9a53a
+        r = cg_all_unified();
b9a53a
+        if (r < 0)
b9a53a
+                return r;
b9a53a
+        if (r == 0)
b9a53a
+                return -ENODATA;
b9a53a
+        if (r > 0)
b9a53a
+                r = cg_get_attribute("cpuset", u->cgroup_path, name, &v);
b9a53a
+        if (r == -ENOENT)
b9a53a
+                return -ENODATA;
b9a53a
+        if (r < 0)
b9a53a
+                return r;
b9a53a
+
b9a53a
+        return parse_cpu_set_full(v, cpus, false, NULL, NULL, 0, NULL);
b9a53a
+}
b9a53a
+
b9a53a
 DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);
b9a53a
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
b9a53a
index 2d2ff6fc3c..da10575394 100644
b9a53a
--- a/src/core/cgroup.h
b9a53a
+++ b/src/core/cgroup.h
b9a53a
@@ -4,6 +4,7 @@
b9a53a
 #include <stdbool.h>
b9a53a
 
b9a53a
 #include "cgroup-util.h"
b9a53a
+#include "cpu-set-util.h"
b9a53a
 #include "ip-address-access.h"
b9a53a
 #include "list.h"
b9a53a
 #include "time-util.h"
b9a53a
@@ -77,6 +78,9 @@ struct CGroupContext {
b9a53a
         uint64_t startup_cpu_weight;
b9a53a
         usec_t cpu_quota_per_sec_usec;
b9a53a
 
b9a53a
+        CPUSet cpuset_cpus;
b9a53a
+        CPUSet cpuset_mems;
b9a53a
+
b9a53a
         uint64_t io_weight;
b9a53a
         uint64_t startup_io_weight;
b9a53a
         LIST_HEAD(CGroupIODeviceWeight, io_device_weights);
b9a53a
@@ -205,3 +209,4 @@ const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_;
b9a53a
 CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_;
b9a53a
 
b9a53a
 bool unit_cgroup_delegate(Unit *u);
b9a53a
+int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
b9a53a
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
b9a53a
index 540bc77aed..30d4e83932 100644
b9a53a
--- a/src/core/dbus-cgroup.c
b9a53a
+++ b/src/core/dbus-cgroup.c
b9a53a
@@ -53,6 +53,27 @@ static int property_get_delegate_controllers(
b9a53a
         return sd_bus_message_close_container(reply);
b9a53a
 }
b9a53a
 
b9a53a
+static int property_get_cpuset(
b9a53a
+                sd_bus *bus,
b9a53a
+                const char *path,
b9a53a
+                const char *interface,
b9a53a
+                const char *property,
b9a53a
+                sd_bus_message *reply,
b9a53a
+                void *userdata,
b9a53a
+                sd_bus_error *error) {
b9a53a
+
b9a53a
+        CPUSet *cpus = userdata;
b9a53a
+        _cleanup_free_ uint8_t *array = NULL;
b9a53a
+        size_t allocated;
b9a53a
+
b9a53a
+        assert(bus);
b9a53a
+        assert(reply);
b9a53a
+        assert(cpus);
b9a53a
+
b9a53a
+        (void) cpu_set_to_dbus(cpus, &array, &allocated);
b9a53a
+        return sd_bus_message_append_array(reply, 'y', array, allocated);
b9a53a
+}
b9a53a
+
b9a53a
 static int property_get_io_device_weight(
b9a53a
                 sd_bus *bus,
b9a53a
                 const char *path,
b9a53a
@@ -283,6 +304,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
b9a53a
         SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0),
b9a53a
         SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0),
b9a53a
         SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0),
b9a53a
+        SD_BUS_PROPERTY("AllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_cpus), 0),
b9a53a
+        SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0),
b9a53a
         SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0),
b9a53a
         SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0),
b9a53a
         SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0),
b9a53a
@@ -671,6 +694,42 @@ int bus_cgroup_set_property(
b9a53a
 
b9a53a
                 return 1;
b9a53a
 
b9a53a
+        } else if (STR_IN_SET(name, "AllowedCPUs", "AllowedMemoryNodes")) {
b9a53a
+                const void *a;
b9a53a
+                size_t n;
b9a53a
+                _cleanup_(cpu_set_reset) CPUSet new_set = {};
b9a53a
+
b9a53a
+                r = sd_bus_message_read_array(message, 'y', &a, &n);
b9a53a
+                if (r < 0)
b9a53a
+                        return r;
b9a53a
+
b9a53a
+                r = cpu_set_from_dbus(a, n, &new_set);
b9a53a
+                if (r < 0)
b9a53a
+                        return r;
b9a53a
+
b9a53a
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
b9a53a
+                        _cleanup_free_ char *setstr = NULL;
b9a53a
+                        _cleanup_free_ char *data = NULL;
b9a53a
+                        CPUSet *set;
b9a53a
+
b9a53a
+                        setstr = cpu_set_to_range_string(&new_set);
b9a53a
+
b9a53a
+                        if (streq(name, "AllowedCPUs"))
b9a53a
+                                set = &c->cpuset_cpus;
b9a53a
+                        else
b9a53a
+                                set = &c->cpuset_mems;
b9a53a
+
b9a53a
+                        if (asprintf(&data, "%s=%s", name, setstr) < 0)
b9a53a
+                                return -ENOMEM;
b9a53a
+
b9a53a
+                        cpu_set_reset(set);
b9a53a
+                        cpu_set_add_all(set, &new_set);
b9a53a
+                        unit_invalidate_cgroup(u, CGROUP_MASK_CPUSET);
b9a53a
+                        unit_write_setting(u, flags, name, data);
b9a53a
+                }
b9a53a
+
b9a53a
+                return 1;
b9a53a
+
b9a53a
         } else if ((iol_type = cgroup_io_limit_type_from_string(name)) >= 0) {
b9a53a
                 const char *path;
b9a53a
                 unsigned n = 0;
b9a53a
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
b9a53a
index c5bca10979..aa15e47754 100644
b9a53a
--- a/src/core/dbus-unit.c
b9a53a
+++ b/src/core/dbus-unit.c
b9a53a
@@ -752,6 +752,52 @@ static int property_get_cpu_usage(
b9a53a
         return sd_bus_message_append(reply, "t", ns);
b9a53a
 }
b9a53a
 
b9a53a
+static int property_get_cpuset_cpus(
b9a53a
+                sd_bus *bus,
b9a53a
+                const char *path,
b9a53a
+                const char *interface,
b9a53a
+                const char *property,
b9a53a
+                sd_bus_message *reply,
b9a53a
+                void *userdata,
b9a53a
+                sd_bus_error *error) {
b9a53a
+
b9a53a
+        Unit *u = userdata;
b9a53a
+        _cleanup_(cpu_set_reset) CPUSet cpus = {};
b9a53a
+        _cleanup_free_ uint8_t *array = NULL;
b9a53a
+        size_t allocated;
b9a53a
+
b9a53a
+        assert(bus);
b9a53a
+        assert(reply);
b9a53a
+        assert(u);
b9a53a
+
b9a53a
+        (void) unit_get_cpuset(u, &cpus, "cpuset.cpus.effective");
b9a53a
+        (void) cpu_set_to_dbus(&cpus, &array, &allocated);
b9a53a
+        return sd_bus_message_append_array(reply, 'y', array, allocated);
b9a53a
+}
b9a53a
+
b9a53a
+static int property_get_cpuset_mems(
b9a53a
+                sd_bus *bus,
b9a53a
+                const char *path,
b9a53a
+                const char *interface,
b9a53a
+                const char *property,
b9a53a
+                sd_bus_message *reply,
b9a53a
+                void *userdata,
b9a53a
+                sd_bus_error *error) {
b9a53a
+
b9a53a
+        Unit *u = userdata;
b9a53a
+        _cleanup_(cpu_set_reset) CPUSet mems = {};
b9a53a
+        _cleanup_free_ uint8_t *array = NULL;
b9a53a
+        size_t allocated;
b9a53a
+
b9a53a
+        assert(bus);
b9a53a
+        assert(reply);
b9a53a
+        assert(u);
b9a53a
+
b9a53a
+        (void) unit_get_cpuset(u, &mems, "cpuset.mems.effective");
b9a53a
+        (void) cpu_set_to_dbus(&mems, &array, &allocated);
b9a53a
+        return sd_bus_message_append_array(reply, 'y', array, allocated);
b9a53a
+}
b9a53a
+
b9a53a
 static int property_get_cgroup(
b9a53a
                 sd_bus *bus,
b9a53a
                 const char *path,
b9a53a
@@ -1074,6 +1120,8 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
b9a53a
         SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0),
b9a53a
         SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
b9a53a
         SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0),
b9a53a
+        SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0),
b9a53a
+        SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0),
b9a53a
         SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
b9a53a
         SD_BUS_PROPERTY("IPIngressBytes", "t", property_get_ip_counter, 0, 0),
b9a53a
         SD_BUS_PROPERTY("IPIngressPackets", "t", property_get_ip_counter, 0, 0),
b9a53a
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
b9a53a
index 49e938d0ce..ebb44df487 100644
b9a53a
--- a/src/core/load-fragment-gperf.gperf.m4
b9a53a
+++ b/src/core/load-fragment-gperf.gperf.m4
b9a53a
@@ -167,6 +167,8 @@ $1.StartupCPUWeight,             config_parse_cg_weight,             0,
b9a53a
 $1.CPUShares,                    config_parse_cpu_shares,            0,                             offsetof($1, cgroup_context.cpu_shares)
b9a53a
 $1.StartupCPUShares,             config_parse_cpu_shares,            0,                             offsetof($1, cgroup_context.startup_cpu_shares)
b9a53a
 $1.CPUQuota,                     config_parse_cpu_quota,             0,                             offsetof($1, cgroup_context)
b9a53a
+$1.CPUSetCpus,                   config_parse_cpuset_cpus,           0,                             offsetof($1, cgroup_context)
b9a53a
+$1.CPUSetMems,                   config_parse_cpuset_mems,           0,                             offsetof($1, cgroup_context)
b9a53a
 $1.MemoryAccounting,             config_parse_bool,                  0,                             offsetof($1, cgroup_context.memory_accounting)
b9a53a
 $1.MemoryLow,                    config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
b9a53a
 $1.MemoryHigh,                   config_parse_memory_limit,          0,                             offsetof($1, cgroup_context)
b9a53a
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
b9a53a
index 35dd595098..6debf82401 100644
b9a53a
--- a/src/core/load-fragment.c
b9a53a
+++ b/src/core/load-fragment.c
b9a53a
@@ -3011,6 +3011,44 @@ int config_parse_cpu_quota(
b9a53a
         return 0;
b9a53a
 }
b9a53a
 
b9a53a
+int config_parse_cpuset_cpus(
b9a53a
+                const char *unit,
b9a53a
+                const char *filename,
b9a53a
+                unsigned line,
b9a53a
+                const char *section,
b9a53a
+                unsigned section_line,
b9a53a
+                const char *lvalue,
b9a53a
+                int ltype,
b9a53a
+                const char *rvalue,
b9a53a
+                void *data,
b9a53a
+                void *userdata) {
b9a53a
+
b9a53a
+        CGroupContext *c = data;
b9a53a
+
b9a53a
+        (void) parse_cpu_set_extend(rvalue, &c->cpuset_cpus, true, unit, filename, line, lvalue);
b9a53a
+
b9a53a
+        return 0;
b9a53a
+}
b9a53a
+
b9a53a
+int config_parse_cpuset_mems(
b9a53a
+                const char *unit,
b9a53a
+                const char *filename,
b9a53a
+                unsigned line,
b9a53a
+                const char *section,
b9a53a
+                unsigned section_line,
b9a53a
+                const char *lvalue,
b9a53a
+                int ltype,
b9a53a
+                const char *rvalue,
b9a53a
+                void *data,
b9a53a
+                void *userdata) {
b9a53a
+
b9a53a
+        CGroupContext *c = data;
b9a53a
+
b9a53a
+        (void) parse_cpu_set_extend(rvalue, &c->cpuset_mems, true, unit, filename, line, lvalue);
b9a53a
+
b9a53a
+        return 0;
b9a53a
+}
b9a53a
+
b9a53a
 int config_parse_memory_limit(
b9a53a
                 const char *unit,
b9a53a
                 const char *filename,
b9a53a
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
b9a53a
index f2ca1b8ee7..6612e1fb32 100644
b9a53a
--- a/src/core/load-fragment.h
b9a53a
+++ b/src/core/load-fragment.h
b9a53a
@@ -86,6 +86,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_cpu_quota);
b9a53a
+CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_cpus);
b9a53a
+CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_mems);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_protect_home);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_protect_system);
b9a53a
 CONFIG_PARSER_PROTOTYPE(config_parse_bus_name);
b9a53a
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
b9a53a
index 3c42e97b7a..8f3b463c6b 100644
b9a53a
--- a/src/shared/bus-unit-util.c
b9a53a
+++ b/src/shared/bus-unit-util.c
b9a53a
@@ -396,6 +396,22 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
b9a53a
 
b9a53a
                 return bus_append_cg_cpu_shares_parse(m, field, eq);
b9a53a
 
b9a53a
+        if (STR_IN_SET(field, "AllowedCPUs", "AllowedMemoryNodes")) {
b9a53a
+                _cleanup_(cpu_set_reset) CPUSet cpuset = {};
b9a53a
+                _cleanup_free_ uint8_t *array = NULL;
b9a53a
+                size_t allocated;
b9a53a
+
b9a53a
+                r = parse_cpu_set(eq, &cpuset);
b9a53a
+                if (r < 0)
b9a53a
+                        return log_error_errno(r, "Failed to parse %s value: %s", field, eq);
b9a53a
+
b9a53a
+                r = cpu_set_to_dbus(&cpuset, &array, &allocated);
b9a53a
+                if (r < 0)
b9a53a
+                        return log_error_errno(r, "Failed to serialize CPUSet: %m");
b9a53a
+
b9a53a
+                return bus_append_byte_array(m, field, array, allocated);
b9a53a
+        }
b9a53a
+
b9a53a
         if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight"))
b9a53a
 
b9a53a
                 return bus_append_cg_blkio_weight_parse(m, field, eq);
b9a53a
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
b9a53a
index 7274921e6d..a3074bc5e3 100644
b9a53a
--- a/src/systemctl/systemctl.c
b9a53a
+++ b/src/systemctl/systemctl.c
b9a53a
@@ -4892,7 +4892,7 @@ static int print_property(const char *name, sd_bus_message *m, bool value, bool
b9a53a
                         print_prop(name, "%s", h);
b9a53a
 
b9a53a
                         return 1;
b9a53a
-                } else if (contents[0] == SD_BUS_TYPE_BYTE && STR_IN_SET(name, "CPUAffinity", "NUMAMask")) {
b9a53a
+                } else if (contents[0] == SD_BUS_TYPE_BYTE && STR_IN_SET(name, "CPUAffinity", "NUMAMask", "AllowedCPUs", "AllowedMemoryNodes", "EffectiveCPUs", "EffectiveMemoryNodes")) {
b9a53a
                         _cleanup_free_ char *affinity = NULL;
b9a53a
                         _cleanup_(cpu_set_reset) CPUSet set = {};
b9a53a
                         const void *a;
b9a53a
diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c
b9a53a
index d65959edf1..93c3f5d856 100644
b9a53a
--- a/src/test/test-cgroup-mask.c
b9a53a
+++ b/src/test/test-cgroup-mask.c
b9a53a
@@ -104,9 +104,10 @@ static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) {
b9a53a
 
b9a53a
 static void test_cg_mask_to_string(void) {
b9a53a
         test_cg_mask_to_string_one(0, NULL);
b9a53a
-        test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct io blkio memory devices pids");
b9a53a
+        test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids");
b9a53a
         test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu");
b9a53a
         test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
b9a53a
+        test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset");
b9a53a
         test_cg_mask_to_string_one(CGROUP_MASK_IO, "io");
b9a53a
         test_cg_mask_to_string_one(CGROUP_MASK_BLKIO, "blkio");
b9a53a
         test_cg_mask_to_string_one(CGROUP_MASK_MEMORY, "memory");