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