Blame SOURCES/0475-core-add-support-for-the-pids-cgroup-controller.patch

17b0f1
From 7d44d0d43465892d4753ff50592588f49d56cf95 Mon Sep 17 00:00:00 2001
17b0f1
From: Lennart Poettering <lennart@poettering.net>
17b0f1
Date: Thu, 10 Sep 2015 12:32:16 +0200
17b0f1
Subject: [PATCH] core: add support for the "pids" cgroup controller
17b0f1
17b0f1
This adds support for the new "pids" cgroup controller of 4.3 kernels.
17b0f1
It allows accounting the number of tasks in a cgroup and enforcing
17b0f1
limits on it.
17b0f1
17b0f1
This adds two new setting TasksAccounting= and TasksMax= to each unit,
17b0f1
as well as a gloabl option DefaultTasksAccounting=.
17b0f1
17b0f1
This also updated "cgtop" to optionally make use of the new
17b0f1
kernel-provided accounting.
17b0f1
17b0f1
systemctl has been updated to show the number of tasks for each service
17b0f1
if it is available.
17b0f1
17b0f1
This patch also adds correct support for undoing memory limits for units
17b0f1
using a MemoryLimit=infinity syntax. We do the same for TasksMax= now
17b0f1
and hence keep things in sync here.
17b0f1
17b0f1
Cherry-picked from: 03a7b521e3ffb7f5d153d90480ba5d4bc29d1e8f
17b0f1
Resolves: #1337244
17b0f1
---
17b0f1
 man/systemd-system.conf.xml           | 22 +++++-----
17b0f1
 man/systemd.resource-control.xml      | 63 ++++++++++++++++++++++-----
17b0f1
 src/core/cgroup.c                     | 44 +++++++++++++++++++
17b0f1
 src/core/cgroup.h                     |  5 +++
17b0f1
 src/core/dbus-cgroup.c                | 41 ++++++++++++++++-
17b0f1
 src/core/dbus-unit.c                  | 25 +++++++++++
17b0f1
 src/core/load-fragment-gperf.gperf.m4 |  2 +
17b0f1
 src/core/load-fragment.c              | 37 ++++++++++++++--
17b0f1
 src/core/load-fragment.h              |  1 +
17b0f1
 src/core/main.c                       |  3 ++
17b0f1
 src/core/manager.h                    |  1 +
17b0f1
 src/core/unit.c                       |  1 +
17b0f1
 src/shared/cgroup-util.h              |  1 +
17b0f1
 src/systemctl/systemctl.c             | 17 ++++++++
17b0f1
 14 files changed, 236 insertions(+), 27 deletions(-)
17b0f1
17b0f1
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
17b0f1
index 57b3b90be1..d367ccd130 100644
17b0f1
--- a/man/systemd-system.conf.xml
17b0f1
+++ b/man/systemd-system.conf.xml
17b0f1
@@ -51,14 +51,14 @@
17b0f1
   </refnamediv>
17b0f1
 
17b0f1
   <refsynopsisdiv>
17b0f1
-    <para><filename>/etc/systemd/system.conf</filename></para>
17b0f1
-    <para><filename>/etc/systemd/system.conf.d/*.conf</filename></para>
17b0f1
-    <para><filename>/run/systemd/system.conf.d/*.conf</filename></para>
17b0f1
-    <para><filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
17b0f1
-    <para><filename>/etc/systemd/user.conf</filename></para>
17b0f1
-    <para><filename>/etc/systemd/user.conf.d/*.conf</filename></para>
17b0f1
-    <para><filename>/run/systemd/user.conf.d/*.conf</filename></para>
17b0f1
-    <para><filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
17b0f1
+    <para><filename>/etc/systemd/system.conf</filename>,
17b0f1
+    <filename>/etc/systemd/system.conf.d/*.conf</filename>,
17b0f1
+    <filename>/run/systemd/system.conf.d/*.conf</filename>,
17b0f1
+    <filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
17b0f1
+    <para><filename>/etc/systemd/user.conf</filename>,
17b0f1
+    <filename>/etc/systemd/user.conf.d/*.conf</filename>,
17b0f1
+    <filename>/run/systemd/user.conf.d/*.conf</filename>,
17b0f1
+    <filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
17b0f1
   </refsynopsisdiv>
17b0f1
 
17b0f1
   <refsect1>
17b0f1
@@ -307,12 +307,14 @@
17b0f1
         <term><varname>DefaultCPUAccounting=</varname></term>
17b0f1
         <term><varname>DefaultBlockIOAccounting=</varname></term>
17b0f1
         <term><varname>DefaultMemoryAccounting=</varname></term>
17b0f1
+        <term><varname>DefaultTasksAccounting=</varname></term>
17b0f1
 
17b0f1
         <listitem><para>Configure the default resource accounting
17b0f1
         settings, as configured per-unit by
17b0f1
         <varname>CPUAccounting=</varname>,
17b0f1
-        <varname>BlockIOAccounting=</varname> and
17b0f1
-        <varname>MemoryAccounting=</varname>. See
17b0f1
+        <varname>BlockIOAccounting=</varname>,
17b0f1
+        <varname>MemoryAccounting=</varname> and
17b0f1
+        <varname>TasksAccounting=</varname>. See
17b0f1
         <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
17b0f1
         for details on the per-unit settings.</para></listitem>
17b0f1
       </varlistentry>
17b0f1
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
17b0f1
index 8f4e7a3f16..6b9329bbee 100644
17b0f1
--- a/man/systemd.resource-control.xml
17b0f1
+++ b/man/systemd.resource-control.xml
17b0f1
@@ -103,10 +103,10 @@
17b0f1
         <listitem>
17b0f1
           <para>Turn on CPU usage accounting for this unit. Takes a
17b0f1
           boolean argument. Note that turning on CPU accounting for
17b0f1
-          one unit might also implicitly turn it on for all units
17b0f1
+          one unit will also implicitly turn it on for all units
17b0f1
           contained in the same slice and for all its parent slices
17b0f1
           and the units contained therein. The system default for this
17b0f1
-          setting maybe controlled with
17b0f1
+          setting may be controlled with
17b0f1
           <varname>DefaultCPUAccounting=</varname> in
17b0f1
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
17b0f1
         </listitem>
17b0f1
@@ -134,7 +134,7 @@
17b0f1
           prioritizing specific services at boot-up differently than
17b0f1
           during normal runtime.</para>
17b0f1
 
17b0f1
-          <para>Those options imply
17b0f1
+          <para>These options imply
17b0f1
           <literal>CPUAccounting=true</literal>.</para>
17b0f1
         </listitem>
17b0f1
       </varlistentry>
17b0f1
@@ -168,9 +168,10 @@
17b0f1
         <listitem>
17b0f1
           <para>Turn on process and kernel memory accounting for this
17b0f1
           unit. Takes a boolean argument. Note that turning on memory
17b0f1
-          accounting for one unit might also implicitly turn it on for
17b0f1
-          all its parent slices. The system default for this setting
17b0f1
-          maybe controlled with
17b0f1
+          accounting for one unit will also implicitly turn it on for
17b0f1
+          all units contained in the same slice and for all its parent
17b0f1
+          slices and the units contained therein. The system default
17b0f1
+          for this setting may be controlled with
17b0f1
           <varname>DefaultMemoryAccounting=</varname> in
17b0f1
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
17b0f1
         </listitem>
17b0f1
@@ -186,26 +187,64 @@
17b0f1
           memory size in bytes. If the value is suffixed with K, M, G
17b0f1
           or T, the specified memory size is parsed as Kilobytes,
17b0f1
           Megabytes, Gigabytes, or Terabytes (with the base 1024),
17b0f1
-          respectively. This controls the
17b0f1
-          <literal>memory.limit_in_bytes</literal> control group
17b0f1
-          attribute. For details about this control group attribute,
17b0f1
-          see 
17b0f1
+          respectively. If assigned the special value
17b0f1
+          <literal>infinity</literal> no memory limit is applied. This
17b0f1
+          controls the <literal>memory.limit_in_bytes</literal>
17b0f1
+          control group attribute. For details about this control
17b0f1
+          group attribute, see 
17b0f1
           url="https://www.kernel.org/doc/Documentation/cgroups/memory.txt">memory.txt</ulink>.</para>
17b0f1
 
17b0f1
           <para>Implies <literal>MemoryAccounting=true</literal>.</para>
17b0f1
         </listitem>
17b0f1
       </varlistentry>
17b0f1
 
17b0f1
+      <varlistentry>
17b0f1
+        <term><varname>TasksAccounting=</varname></term>
17b0f1
+
17b0f1
+        <listitem>
17b0f1
+          <para>Turn on task accounting for this unit. Takes a
17b0f1
+          boolean argument. If enabled, the system manager will keep
17b0f1
+          track of the number of tasks in the unit. The number of
17b0f1
+          tasks accounted this way includes both kernel threads and
17b0f1
+          userspace processes, with each thread counting
17b0f1
+          individually. Note that turning on tasks accounting for one
17b0f1
+          unit will also implicitly turn it on for all units contained
17b0f1
+          in the same slice and for all its parent slices and the
17b0f1
+          units contained therein. The system default for this setting
17b0f1
+          may be controlled with
17b0f1
+          <varname>DefaultTasksAccounting=</varname> in
17b0f1
+          <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
17b0f1
+        </listitem>
17b0f1
+      </varlistentry>
17b0f1
+
17b0f1
+      <varlistentry>
17b0f1
+        <term><varname>TasksMax=<replaceable>N</replaceable></varname></term>
17b0f1
+
17b0f1
+        <listitem>
17b0f1
+          <para>Specify the maximum number of tasks that may be
17b0f1
+          created in the unit. This ensures that the number of tasks
17b0f1
+          accounted for the unit (see above) stays below a specific
17b0f1
+          limit. If assigned the special value
17b0f1
+          <literal>infinity</literal> no tasks limit is applied. This
17b0f1
+          controls the <literal>pids.max</literal> control group
17b0f1
+          attribute. For details about this control group attribute,
17b0f1
+          see 
17b0f1
+          url="https://www.kernel.org/doc/Documentation/cgroups/pids.txt">pids.txt</ulink>.</para>
17b0f1
+
17b0f1
+          <para>Implies <literal>TasksAccounting=true</literal>.</para>
17b0f1
+        </listitem>
17b0f1
+      </varlistentry>
17b0f1
+
17b0f1
       <varlistentry>
17b0f1
         <term><varname>BlockIOAccounting=</varname></term>
17b0f1
 
17b0f1
         <listitem>
17b0f1
           <para>Turn on Block IO accounting for this unit. Takes a
17b0f1
           boolean argument. Note that turning on block IO accounting
17b0f1
-          for one unit might also implicitly turn it on for all units
17b0f1
+          for one unit will also implicitly turn it on for all units
17b0f1
           contained in the same slice and all for its parent slices
17b0f1
           and the units contained therein. The system default for this
17b0f1
-          setting maybe controlled with
17b0f1
+          setting may be controlled with
17b0f1
           <varname>DefaultBlockIOAccounting=</varname> in
17b0f1
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
17b0f1
         </listitem>
17b0f1
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
17b0f1
index b7f08fb420..d4a8f9cbe3 100644
17b0f1
--- a/src/core/cgroup.c
17b0f1
+++ b/src/core/cgroup.c
17b0f1
@@ -40,6 +40,7 @@ void cgroup_context_init(CGroupContext *c) {
17b0f1
         c->memory_limit = (uint64_t) -1;
17b0f1
         c->blockio_weight = (unsigned long) -1;
17b0f1
         c->startup_blockio_weight = (unsigned long) -1;
17b0f1
+        c->tasks_max = (uint64_t) -1;
17b0f1
 
17b0f1
         c->cpu_quota_per_sec_usec = USEC_INFINITY;
17b0f1
 }
17b0f1
@@ -105,6 +106,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
17b0f1
                 "%sBlockIOWeight=%lu\n"
17b0f1
                 "%sStartupBlockIOWeight=%lu\n"
17b0f1
                 "%sMemoryLimit=%" PRIu64 "\n"
17b0f1
+                "%sTasksMax=%" PRIu64 "\n"
17b0f1
                 "%sDevicePolicy=%s\n"
17b0f1
                 "%sDelegate=%s\n",
17b0f1
                 prefix, yes_no(c->cpu_accounting),
17b0f1
@@ -116,6 +118,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
17b0f1
                 prefix, c->blockio_weight,
17b0f1
                 prefix, c->startup_blockio_weight,
17b0f1
                 prefix, c->memory_limit,
17b0f1
+                prefix, c->tasks_max,
17b0f1
                 prefix, cgroup_device_policy_to_string(c->device_policy),
17b0f1
                 prefix, yes_no(c->delegate));
17b0f1
 
17b0f1
@@ -456,6 +459,21 @@ void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const cha
17b0f1
                                 log_debug("Ignoring device %s while writing cgroup attribute.", a->path);
17b0f1
                 }
17b0f1
         }
17b0f1
+
17b0f1
+        if ((mask & CGROUP_PIDS) && !is_root) {
17b0f1
+
17b0f1
+                if (c->tasks_max != (uint64_t) -1) {
17b0f1
+                        char buf[DECIMAL_STR_MAX(uint64_t) + 2];
17b0f1
+
17b0f1
+                        sprintf(buf, "%" PRIu64 "\n", c->tasks_max);
17b0f1
+                        r = cg_set_attribute("pids", path, "pids.max", buf);
17b0f1
+                } else
17b0f1
+                        r = cg_set_attribute("pids", path, "pids.max", "max");
17b0f1
+
17b0f1
+                if (r < 0)
17b0f1
+                        log_full_errno(IN_SET(r, -ENOENT, -EROFS) ? LOG_DEBUG : LOG_WARNING, r,
17b0f1
+                                       "Failed to set pids.max on %s: %m", path);
17b0f1
+        }
17b0f1
 }
17b0f1
 
17b0f1
 CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
17b0f1
@@ -484,6 +502,10 @@ CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
17b0f1
             c->device_policy != CGROUP_AUTO)
17b0f1
                 mask |= CGROUP_DEVICE;
17b0f1
 
17b0f1
+        if (c->tasks_accounting ||
17b0f1
+            c->tasks_max != (uint64_t) -1)
17b0f1
+                mask |= CGROUP_PIDS;
17b0f1
+
17b0f1
         return mask;
17b0f1
 }
17b0f1
 
17b0f1
@@ -1044,6 +1066,28 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
17b0f1
         return 0;
17b0f1
 }
17b0f1
 
17b0f1
+int unit_get_tasks_current(Unit *u, uint64_t *ret) {
17b0f1
+        _cleanup_free_ char *v = NULL;
17b0f1
+        int r;
17b0f1
+
17b0f1
+        assert(u);
17b0f1
+       assert(ret);
17b0f1
+
17b0f1
+        if (!u->cgroup_path)
17b0f1
+                return -ENODATA;
17b0f1
+
17b0f1
+        if ((u->cgroup_realized_mask & CGROUP_PIDS) == 0)
17b0f1
+                return -ENODATA;
17b0f1
+
17b0f1
+        r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v);
17b0f1
+        if (r == -ENOENT)
17b0f1
+                return -ENODATA;
17b0f1
+        if (r < 0)
17b0f1
+                return r;
17b0f1
+
17b0f1
+        return safe_atou64(v, ret);
17b0f1
+}
17b0f1
+
17b0f1
 static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
17b0f1
         [CGROUP_AUTO] = "auto",
17b0f1
         [CGROUP_CLOSED] = "closed",
17b0f1
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
17b0f1
index 8fa851de32..8af3eaa3ae 100644
17b0f1
--- a/src/core/cgroup.h
17b0f1
+++ b/src/core/cgroup.h
17b0f1
@@ -72,6 +72,7 @@ struct CGroupContext {
17b0f1
         bool cpu_accounting;
17b0f1
         bool blockio_accounting;
17b0f1
         bool memory_accounting;
17b0f1
+        bool tasks_accounting;
17b0f1
 
17b0f1
         unsigned long cpu_shares;
17b0f1
         unsigned long startup_cpu_shares;
17b0f1
@@ -88,6 +89,8 @@ struct CGroupContext {
17b0f1
         LIST_HEAD(CGroupDeviceAllow, device_allow);
17b0f1
 
17b0f1
         bool delegate;
17b0f1
+
17b0f1
+        uint64_t tasks_max;
17b0f1
 };
17b0f1
 
17b0f1
 #include "unit.h"
17b0f1
@@ -127,5 +130,7 @@ pid_t unit_search_main_pid(Unit *u);
17b0f1
 
17b0f1
 int manager_notify_cgroup_empty(Manager *m, const char *group);
17b0f1
 
17b0f1
+int unit_get_tasks_current(Unit *u, uint64_t *ret);
17b0f1
+
17b0f1
 const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_;
17b0f1
 CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_;
17b0f1
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
17b0f1
index 4a9df06016..a4465dc7aa 100644
17b0f1
--- a/src/core/dbus-cgroup.c
17b0f1
+++ b/src/core/dbus-cgroup.c
17b0f1
@@ -168,6 +168,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
17b0f1
         SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0),
17b0f1
         SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0),
17b0f1
         SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0),
17b0f1
+        SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0),
17b0f1
+        SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0),
17b0f1
         SD_BUS_VTABLE_END
17b0f1
 };
17b0f1
 
17b0f1
@@ -551,7 +553,11 @@ int bus_cgroup_set_property(
17b0f1
                 if (mode != UNIT_CHECK) {
17b0f1
                         c->memory_limit = limit;
17b0f1
                         u->cgroup_realized_mask &= ~CGROUP_MEMORY;
17b0f1
-                        unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, limit);
17b0f1
+
17b0f1
+                        if (limit == (uint64_t) -1)
17b0f1
+                                unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity");
17b0f1
+                        else
17b0f1
+                                unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit);
17b0f1
                 }
17b0f1
 
17b0f1
                 return 1;
17b0f1
@@ -667,6 +673,39 @@ int bus_cgroup_set_property(
17b0f1
 
17b0f1
                 return 1;
17b0f1
 
17b0f1
+        } else if (streq(name, "TasksAccounting")) {
17b0f1
+                int b;
17b0f1
+
17b0f1
+                r = sd_bus_message_read(message, "b", &b);
17b0f1
+                if (r < 0)
17b0f1
+                        return r;
17b0f1
+
17b0f1
+                if (mode != UNIT_CHECK) {
17b0f1
+                        c->tasks_accounting = b;
17b0f1
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
17b0f1
+                        unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no");
17b0f1
+                }
17b0f1
+
17b0f1
+                return 1;
17b0f1
+
17b0f1
+        } else if (streq(name, "TasksMax")) {
17b0f1
+                uint64_t limit;
17b0f1
+
17b0f1
+                r = sd_bus_message_read(message, "t", &limit);
17b0f1
+                if (r < 0)
17b0f1
+                        return r;
17b0f1
+
17b0f1
+                if (mode != UNIT_CHECK) {
17b0f1
+                        c->tasks_max = limit;
17b0f1
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
17b0f1
+
17b0f1
+                        if (limit == (uint64_t) -1)
17b0f1
+                                unit_write_drop_in_private(u, mode, name, "TasksMax=infinity");
17b0f1
+                        else
17b0f1
+                                unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit);
17b0f1
+                }
17b0f1
+
17b0f1
+                return 1;
17b0f1
         }
17b0f1
 
17b0f1
         if (u->transient && u->load_state == UNIT_STUB) {
17b0f1
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
17b0f1
index 056a17ac1f..1d0d6f67c7 100644
17b0f1
--- a/src/core/dbus-unit.c
17b0f1
+++ b/src/core/dbus-unit.c
17b0f1
@@ -673,11 +673,36 @@ static int property_get_current_memory(
17b0f1
         return sd_bus_message_append(reply, "t", sz);
17b0f1
 }
17b0f1
 
17b0f1
+static int property_get_current_tasks(
17b0f1
+                sd_bus *bus,
17b0f1
+                const char *path,
17b0f1
+                const char *interface,
17b0f1
+                const char *property,
17b0f1
+                sd_bus_message *reply,
17b0f1
+                void *userdata,
17b0f1
+                sd_bus_error *error) {
17b0f1
+
17b0f1
+        uint64_t cn = (uint64_t) -1;
17b0f1
+        Unit *u = userdata;
17b0f1
+        int r;
17b0f1
+
17b0f1
+        assert(bus);
17b0f1
+        assert(reply);
17b0f1
+        assert(u);
17b0f1
+
17b0f1
+        r = unit_get_tasks_current(u, &cn;;
17b0f1
+        if (r < 0 && r != -ENODATA)
17b0f1
+                log_unit_warning_errno(u->id, r, "Failed to get pids.current attribute: %m");
17b0f1
+
17b0f1
+        return sd_bus_message_append(reply, "t", cn);
17b0f1
+}
17b0f1
+
17b0f1
 const sd_bus_vtable bus_unit_cgroup_vtable[] = {
17b0f1
         SD_BUS_VTABLE_START(0),
17b0f1
         SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
17b0f1
         SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Unit, cgroup_path), 0),
17b0f1
         SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
17b0f1
+        SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
17b0f1
         SD_BUS_VTABLE_END
17b0f1
 };
17b0f1
 
17b0f1
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
17b0f1
index f996032cf2..26e4c618ee 100644
17b0f1
--- a/src/core/load-fragment-gperf.gperf.m4
17b0f1
+++ b/src/core/load-fragment-gperf.gperf.m4
17b0f1
@@ -125,6 +125,8 @@ $1.StartupBlockIOWeight,         config_parse_blockio_weight,        0,
17b0f1
 $1.BlockIODeviceWeight,          config_parse_blockio_device_weight, 0,                             offsetof($1, cgroup_context)
17b0f1
 $1.BlockIOReadBandwidth,         config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
17b0f1
 $1.BlockIOWriteBandwidth,        config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
17b0f1
+$1.TasksAccounting,              config_parse_bool,                  0,                             offsetof($1, cgroup_context.tasks_accounting)
17b0f1
+$1.TasksMax,                     config_parse_tasks_max,             0,                             offsetof($1, cgroup_context)
17b0f1
 $1.Delegate,                     config_parse_bool,                  0,                             offsetof($1, cgroup_context.delegate)'
17b0f1
 )m4_dnl
17b0f1
 Unit.Description,                config_parse_unit_string_printf,    0,                             offsetof(Unit, description)
17b0f1
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
17b0f1
index 7d1ac6c251..7d2e737d05 100644
17b0f1
--- a/src/core/load-fragment.c
17b0f1
+++ b/src/core/load-fragment.c
17b0f1
@@ -3052,7 +3052,7 @@ int config_parse_memory_limit(
17b0f1
         off_t bytes;
17b0f1
         int r;
17b0f1
 
17b0f1
-        if (isempty(rvalue)) {
17b0f1
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
17b0f1
                 c->memory_limit = (uint64_t) -1;
17b0f1
                 return 0;
17b0f1
         }
17b0f1
@@ -3060,9 +3060,8 @@ int config_parse_memory_limit(
17b0f1
         assert_cc(sizeof(uint64_t) == sizeof(off_t));
17b0f1
 
17b0f1
         r = parse_size(rvalue, 1024, &bytes);
17b0f1
-        if (r < 0) {
17b0f1
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
17b0f1
-                           "Memory limit '%s' invalid. Ignoring.", rvalue);
17b0f1
+        if (r < 0 || bytes < 1) {
17b0f1
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Memory limit '%s' invalid. Ignoring.", rvalue);
17b0f1
                 return 0;
17b0f1
         }
17b0f1
 
17b0f1
@@ -3070,6 +3069,36 @@ int config_parse_memory_limit(
17b0f1
         return 0;
17b0f1
 }
17b0f1
 
17b0f1
+int config_parse_tasks_max(
17b0f1
+                const char *unit,
17b0f1
+                const char *filename,
17b0f1
+                unsigned line,
17b0f1
+                const char *section,
17b0f1
+                unsigned section_line,
17b0f1
+                const char *lvalue,
17b0f1
+                int ltype,
17b0f1
+                const char *rvalue,
17b0f1
+                void *data,
17b0f1
+                void *userdata) {
17b0f1
+
17b0f1
+        CGroupContext *c = data;
17b0f1
+        uint64_t u;
17b0f1
+        int r;
17b0f1
+
17b0f1
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
17b0f1
+                c->tasks_max = (uint64_t) -1;
17b0f1
+                return 0;
17b0f1
+        }
17b0f1
+
17b0f1
+        r = safe_atou64(rvalue, &u);
17b0f1
+        if (r < 0 || u < 1) {
17b0f1
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Maximum tasks value '%s' invalid. Ignoring.", rvalue);
17b0f1
+                return 0;
17b0f1
+        }
17b0f1
+
17b0f1
+        return 0;
17b0f1
+}
17b0f1
+
17b0f1
 int config_parse_device_allow(
17b0f1
                 const char *unit,
17b0f1
                 const char *filename,
17b0f1
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
17b0f1
index 2059353d38..8d334f2c86 100644
17b0f1
--- a/src/core/load-fragment.h
17b0f1
+++ b/src/core/load-fragment.h
17b0f1
@@ -89,6 +89,7 @@ int config_parse_pass_environ(const char *unit, const char *filename, unsigned l
17b0f1
 int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
 int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
 int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
+int config_parse_tasks_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
 int config_parse_device_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
 int config_parse_device_allow(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
 int config_parse_blockio_weight(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
17b0f1
diff --git a/src/core/main.c b/src/core/main.c
17b0f1
index cba992cea2..aca05a5358 100644
17b0f1
--- a/src/core/main.c
17b0f1
+++ b/src/core/main.c
17b0f1
@@ -117,6 +117,7 @@ static bool arg_default_cpu_accounting = false;
17b0f1
 static bool arg_default_blockio_accounting = false;
17b0f1
 static bool arg_default_memory_accounting = false;
17b0f1
 static EmergencyAction arg_cad_burst_action = EMERGENCY_ACTION_REBOOT_FORCE;
17b0f1
+static bool arg_default_tasks_accounting = false;
17b0f1
 
17b0f1
 static void nop_handler(int sig) {}
17b0f1
 
17b0f1
@@ -676,6 +677,7 @@ static int parse_config_file(void) {
17b0f1
                 { "Manager", "DefaultBlockIOAccounting",  config_parse_bool,             0, &arg_default_blockio_accounting        },
17b0f1
                 { "Manager", "DefaultMemoryAccounting",   config_parse_bool,             0, &arg_default_memory_accounting         },
17b0f1
                 { "Manager", "CtrlAltDelBurstAction",     config_parse_emergency_action, 0, &arg_cad_burst_action                  },
17b0f1
+                { "Manager", "DefaultTasksAccounting",    config_parse_bool,             0, &arg_default_tasks_accounting          },
17b0f1
                 {}
17b0f1
         };
17b0f1
 
17b0f1
@@ -1685,6 +1687,7 @@ int main(int argc, char *argv[]) {
17b0f1
         m->default_cpu_accounting = arg_default_cpu_accounting;
17b0f1
         m->default_blockio_accounting = arg_default_blockio_accounting;
17b0f1
         m->default_memory_accounting = arg_default_memory_accounting;
17b0f1
+        m->default_tasks_accounting = arg_default_tasks_accounting;
17b0f1
         m->runtime_watchdog = arg_runtime_watchdog;
17b0f1
         m->shutdown_watchdog = arg_shutdown_watchdog;
17b0f1
 
17b0f1
diff --git a/src/core/manager.h b/src/core/manager.h
17b0f1
index 231c076b10..96dcd83dc0 100644
17b0f1
--- a/src/core/manager.h
17b0f1
+++ b/src/core/manager.h
17b0f1
@@ -261,6 +261,7 @@ struct Manager {
17b0f1
         bool default_cpu_accounting;
17b0f1
         bool default_memory_accounting;
17b0f1
         bool default_blockio_accounting;
17b0f1
+        bool default_tasks_accounting;
17b0f1
 
17b0f1
         usec_t default_timer_accuracy_usec;
17b0f1
 
17b0f1
diff --git a/src/core/unit.c b/src/core/unit.c
17b0f1
index 103f92084c..2fcb4fbf07 100644
17b0f1
--- a/src/core/unit.c
17b0f1
+++ b/src/core/unit.c
17b0f1
@@ -126,6 +126,7 @@ static void unit_init(Unit *u) {
17b0f1
                 cc->cpu_accounting = u->manager->default_cpu_accounting;
17b0f1
                 cc->blockio_accounting = u->manager->default_blockio_accounting;
17b0f1
                 cc->memory_accounting = u->manager->default_memory_accounting;
17b0f1
+                cc->tasks_accounting = u->manager->default_tasks_accounting;
17b0f1
         }
17b0f1
 
17b0f1
         ec = unit_get_exec_context(u);
17b0f1
diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h
17b0f1
index 96a3d3bafa..31bd8d311f 100644
17b0f1
--- a/src/shared/cgroup-util.h
17b0f1
+++ b/src/shared/cgroup-util.h
17b0f1
@@ -35,6 +35,7 @@ typedef enum CGroupControllerMask {
17b0f1
         CGROUP_BLKIO = 4,
17b0f1
         CGROUP_MEMORY = 8,
17b0f1
         CGROUP_DEVICE = 16,
17b0f1
+        CGROUP_PIDS = 32,
17b0f1
         _CGROUP_CONTROLLER_MASK_ALL = 31
17b0f1
 } CGroupControllerMask;
17b0f1
 
17b0f1
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
17b0f1
index 0333599c87..b1862b5676 100644
17b0f1
--- a/src/systemctl/systemctl.c
17b0f1
+++ b/src/systemctl/systemctl.c
17b0f1
@@ -3280,6 +3280,8 @@ typedef struct UnitStatusInfo {
17b0f1
         /* CGroup */
17b0f1
         uint64_t memory_current;
17b0f1
         uint64_t memory_limit;
17b0f1
+        uint64_t tasks_current;
17b0f1
+        uint64_t tasks_max;
17b0f1
 
17b0f1
         LIST_HEAD(ExecStatusInfo, exec);
17b0f1
 } UnitStatusInfo;
17b0f1
@@ -3539,6 +3541,15 @@ static void print_status_info(
17b0f1
         if (i->status_errno > 0)
17b0f1
                 printf("    Error: %i (%s)\n", i->status_errno, strerror(i->status_errno));
17b0f1
 
17b0f1
+        if (i->tasks_current != (uint64_t) -1) {
17b0f1
+                printf("    Tasks: %" PRIu64, i->tasks_current);
17b0f1
+
17b0f1
+                if (i->tasks_max != (uint64_t) -1)
17b0f1
+                        printf(" (limit: %" PRIi64 ")\n", i->tasks_max);
17b0f1
+                else
17b0f1
+                        printf("\n");
17b0f1
+        }
17b0f1
+
17b0f1
         if (i->memory_current != (uint64_t) -1) {
17b0f1
                 char buf[FORMAT_BYTES_MAX];
17b0f1
 
17b0f1
@@ -3768,6 +3779,10 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *
17b0f1
                         i->memory_current = u;
17b0f1
                 else if (streq(name, "MemoryLimit"))
17b0f1
                         i->memory_limit = u;
17b0f1
+                else if (streq(name, "TasksCurrent"))
17b0f1
+                        i->tasks_current = u;
17b0f1
+                else if (streq(name, "TasksMax"))
17b0f1
+                        i->tasks_max = u;
17b0f1
 
17b0f1
                 break;
17b0f1
         }
17b0f1
@@ -4248,6 +4263,8 @@ static int show_one(
17b0f1
         UnitStatusInfo info = {
17b0f1
                 .memory_current = (uint64_t) -1,
17b0f1
                 .memory_limit = (uint64_t) -1,
17b0f1
+                .tasks_current = (uint64_t) -1,
17b0f1
+                .tasks_max = (uint64_t) -1,
17b0f1
         };
17b0f1
         ExecStatusInfo *p;
17b0f1
         int r;