naccyde / rpms / systemd

Forked from rpms/systemd a year ago
Clone
c2dfb7
From 2240e7955d64260e94dd52a3ab9855d267c2af89 Mon Sep 17 00:00:00 2001
c2dfb7
From: Tejun Heo <tj@kernel.org>
c2dfb7
Date: Wed, 13 Jun 2018 14:16:35 -0700
c2dfb7
Subject: [PATCH] core: add IODeviceLatencyTargetSec
c2dfb7
c2dfb7
This adds support for the following proposed latency based IO control
c2dfb7
mechanism.
c2dfb7
c2dfb7
  https://lkml.org/lkml/2018/6/5/428
c2dfb7
c2dfb7
(cherry picked from commit 6ae4283cb14c4e4a895f4bbba703804e4128c86c)
c2dfb7
c2dfb7
Resolves: #1831519
c2dfb7
---
c2dfb7
 man/systemd.resource-control.xml      |  29 +++++--
c2dfb7
 src/core/cgroup.c                     |  56 +++++++++++--
c2dfb7
 src/core/cgroup.h                     |   9 +++
c2dfb7
 src/core/dbus-cgroup.c                | 111 ++++++++++++++++++++++++++
c2dfb7
 src/core/load-fragment-gperf.gperf.m4 |   1 +
c2dfb7
 src/core/load-fragment.c              |  72 +++++++++++++++++
c2dfb7
 src/core/load-fragment.h              |   1 +
c2dfb7
 src/shared/bus-unit-util.c            |  31 +++++++
c2dfb7
 src/systemctl/systemctl.c             |  22 +++++
c2dfb7
 9 files changed, 320 insertions(+), 12 deletions(-)
c2dfb7
c2dfb7
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
c2dfb7
index 4329742e94..b0064bf98f 100644
c2dfb7
--- a/man/systemd.resource-control.xml
c2dfb7
+++ b/man/systemd.resource-control.xml
c2dfb7
@@ -417,11 +417,11 @@
c2dfb7
         <listitem>
c2dfb7
           <para>Set the per-device overall block I/O weight for the executed processes, if the unified control group
c2dfb7
           hierarchy is used on the system. Takes a space-separated pair of a file path and a weight value to specify
c2dfb7
-          the device specific weight value, between 1 and 10000. (Example: "/dev/sda 1000"). The file path may be
c2dfb7
-          specified as path to a block device node or as any other file, in which case the backing block device of the
c2dfb7
-          file system of the file is determined. This controls the <literal>io.weight</literal> control group
c2dfb7
-          attribute, which defaults to 100. Use this option multiple times to set weights for multiple devices. For
c2dfb7
-          details about this control group attribute, see 
c2dfb7
+          the device specific weight value, between 1 and 10000. (Example: <literal>/dev/sda 1000</literal>). The file
c2dfb7
+          path may be specified as path to a block device node or as any other file, in which case the backing block
c2dfb7
+          device of the file system of the file is determined. This controls the <literal>io.weight</literal> control
c2dfb7
+          group attribute, which defaults to 100. Use this option multiple times to set weights for multiple devices.
c2dfb7
+          For details about this control group attribute, see 
c2dfb7
           url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
c2dfb7
 
c2dfb7
           <para>Implies <literal>IOAccounting=true</literal>.</para>
c2dfb7
@@ -482,6 +482,25 @@
c2dfb7
         </listitem>
c2dfb7
       </varlistentry>
c2dfb7
 
c2dfb7
+      <varlistentry>
c2dfb7
+        <term><varname>IODeviceLatencyTargetSec=<replaceable>device</replaceable> <replaceable>target</replaceable></varname></term>
c2dfb7
+
c2dfb7
+        <listitem>
c2dfb7
+          <para>Set the per-device average target I/O latency for the executed processes, if the unified control group
c2dfb7
+          hierarchy is used on the system. Takes a file path and a timespan separated by a space to specify
c2dfb7
+          the device specific latency target. (Example: "/dev/sda 25ms"). The file path may be specified
c2dfb7
+          as path to a block device node or as any other file, in which case the backing block device of the file
c2dfb7
+          system of the file is determined. This controls the <literal>io.latency</literal> control group
c2dfb7
+          attribute. Use this option multiple times to set latency target for multiple devices. For details about this
c2dfb7
+          control group attribute, see 
c2dfb7
+          url="https://www.kernel.org/doc/Documentation/cgroup-v2.txt">cgroup-v2.txt</ulink>.</para>
c2dfb7
+
c2dfb7
+          <para>Implies <literal>IOAccounting=true</literal>.</para>
c2dfb7
+
c2dfb7
+          <para>These settings are supported only if the unified control group hierarchy is used.</para>
c2dfb7
+        </listitem>
c2dfb7
+      </varlistentry>
c2dfb7
+
c2dfb7
       <varlistentry>
c2dfb7
         <term><varname>IPAccounting=</varname></term>
c2dfb7
 
c2dfb7
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
c2dfb7
index 9e4c3c7dac..ad8219bd79 100644
c2dfb7
--- a/src/core/cgroup.c
c2dfb7
+++ b/src/core/cgroup.c
c2dfb7
@@ -114,6 +114,15 @@ void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight
c2dfb7
         free(w);
c2dfb7
 }
c2dfb7
 
c2dfb7
+void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLatency *l) {
c2dfb7
+        assert(c);
c2dfb7
+        assert(l);
c2dfb7
+
c2dfb7
+        LIST_REMOVE(device_latencies, c->io_device_latencies, l);
c2dfb7
+        free(l->path);
c2dfb7
+        free(l);
c2dfb7
+}
c2dfb7
+
c2dfb7
 void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l) {
c2dfb7
         assert(c);
c2dfb7
         assert(l);
c2dfb7
@@ -147,6 +156,9 @@ void cgroup_context_done(CGroupContext *c) {
c2dfb7
         while (c->io_device_weights)
c2dfb7
                 cgroup_context_free_io_device_weight(c, c->io_device_weights);
c2dfb7
 
c2dfb7
+        while (c->io_device_latencies)
c2dfb7
+                cgroup_context_free_io_device_latency(c, c->io_device_latencies);
c2dfb7
+
c2dfb7
         while (c->io_device_limits)
c2dfb7
                 cgroup_context_free_io_device_limit(c, c->io_device_limits);
c2dfb7
 
c2dfb7
@@ -171,6 +183,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
c2dfb7
         _cleanup_free_ char *cpuset_mems = NULL;
c2dfb7
         CGroupIODeviceLimit *il;
c2dfb7
         CGroupIODeviceWeight *iw;
c2dfb7
+        CGroupIODeviceLatency *l;
c2dfb7
         CGroupBlockIODeviceBandwidth *b;
c2dfb7
         CGroupBlockIODeviceWeight *w;
c2dfb7
         CGroupDeviceAllow *a;
c2dfb7
@@ -256,11 +269,18 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
c2dfb7
 
c2dfb7
         LIST_FOREACH(device_weights, iw, c->io_device_weights)
c2dfb7
                 fprintf(f,
c2dfb7
-                        "%sIODeviceWeight=%s %" PRIu64,
c2dfb7
+                        "%sIODeviceWeight=%s %" PRIu64 "\n",
c2dfb7
                         prefix,
c2dfb7
                         iw->path,
c2dfb7
                         iw->weight);
c2dfb7
 
c2dfb7
+        LIST_FOREACH(device_latencies, l, c->io_device_latencies)
c2dfb7
+                fprintf(f,
c2dfb7
+                        "%sIODeviceLatencyTargetSec=%s %s\n",
c2dfb7
+                        prefix,
c2dfb7
+                        l->path,
c2dfb7
+                        format_timespan(u, sizeof(u), l->target_usec, 1));
c2dfb7
+
c2dfb7
         LIST_FOREACH(device_limits, il, c->io_device_limits) {
c2dfb7
                 char buf[FORMAT_BYTES_MAX];
c2dfb7
                 CGroupIOLimitType type;
c2dfb7
@@ -573,6 +593,7 @@ static bool cgroup_context_has_io_config(CGroupContext *c) {
c2dfb7
                 c->io_weight != CGROUP_WEIGHT_INVALID ||
c2dfb7
                 c->startup_io_weight != CGROUP_WEIGHT_INVALID ||
c2dfb7
                 c->io_device_weights ||
c2dfb7
+                c->io_device_latencies ||
c2dfb7
                 c->io_device_limits;
c2dfb7
 }
c2dfb7
 
c2dfb7
@@ -646,6 +667,26 @@ static void cgroup_apply_blkio_device_weight(Unit *u, const char *dev_path, uint
c2dfb7
                               "Failed to set blkio.weight_device: %m");
c2dfb7
 }
c2dfb7
 
c2dfb7
+static void cgroup_apply_io_device_latency(Unit *u, const char *dev_path, usec_t target) {
c2dfb7
+        char buf[DECIMAL_STR_MAX(dev_t)*2+2+7+DECIMAL_STR_MAX(uint64_t)+1];
c2dfb7
+        dev_t dev;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        r = lookup_block_device(dev_path, &dev;;
c2dfb7
+        if (r < 0)
c2dfb7
+                return;
c2dfb7
+
c2dfb7
+        if (target != USEC_INFINITY)
c2dfb7
+                xsprintf(buf, "%u:%u target=%" PRIu64 "\n", major(dev), minor(dev), target);
c2dfb7
+        else
c2dfb7
+                xsprintf(buf, "%u:%u target=max\n", major(dev), minor(dev));
c2dfb7
+
c2dfb7
+        r = cg_set_attribute("io", u->cgroup_path, "io.latency", 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 io.latency on cgroup %s: %m", u->cgroup_path);
c2dfb7
+}
c2dfb7
+
c2dfb7
 static void cgroup_apply_io_device_limit(Unit *u, const char *dev_path, uint64_t *limits) {
c2dfb7
         char limit_bufs[_CGROUP_IO_LIMIT_TYPE_MAX][DECIMAL_STR_MAX(uint64_t)];
c2dfb7
         char buf[DECIMAL_STR_MAX(dev_t)*2+2+(6+DECIMAL_STR_MAX(uint64_t)+1)*4];
c2dfb7
@@ -827,13 +868,11 @@ static void cgroup_context_apply(
c2dfb7
                         if (has_io) {
c2dfb7
                                 CGroupIODeviceWeight *w;
c2dfb7
 
c2dfb7
-                                /* FIXME: no way to reset this list */
c2dfb7
                                 LIST_FOREACH(device_weights, w, c->io_device_weights)
c2dfb7
                                         cgroup_apply_io_device_weight(u, w->path, w->weight);
c2dfb7
                         } else if (has_blockio) {
c2dfb7
                                 CGroupBlockIODeviceWeight *w;
c2dfb7
 
c2dfb7
-                                /* FIXME: no way to reset this list */
c2dfb7
                                 LIST_FOREACH(device_weights, w, c->blockio_device_weights) {
c2dfb7
                                         weight = cgroup_weight_blkio_to_io(w->weight);
c2dfb7
 
c2dfb7
@@ -843,9 +882,15 @@ static void cgroup_context_apply(
c2dfb7
                                         cgroup_apply_io_device_weight(u, w->path, weight);
c2dfb7
                                 }
c2dfb7
                         }
c2dfb7
+
c2dfb7
+                        if (has_io) {
c2dfb7
+                                CGroupIODeviceLatency *l;
c2dfb7
+
c2dfb7
+                                LIST_FOREACH(device_latencies, l, c->io_device_latencies)
c2dfb7
+                                        cgroup_apply_io_device_latency(u, l->path, l->target_usec);
c2dfb7
+                        }
c2dfb7
                 }
c2dfb7
 
c2dfb7
-                /* Apply limits and free ones without config. */
c2dfb7
                 if (has_io) {
c2dfb7
                         CGroupIODeviceLimit *l;
c2dfb7
 
c2dfb7
@@ -902,7 +947,6 @@ static void cgroup_context_apply(
c2dfb7
                         if (has_io) {
c2dfb7
                                 CGroupIODeviceWeight *w;
c2dfb7
 
c2dfb7
-                                /* FIXME: no way to reset this list */
c2dfb7
                                 LIST_FOREACH(device_weights, w, c->io_device_weights) {
c2dfb7
                                         weight = cgroup_weight_io_to_blkio(w->weight);
c2dfb7
 
c2dfb7
@@ -914,13 +958,11 @@ static void cgroup_context_apply(
c2dfb7
                         } else if (has_blockio) {
c2dfb7
                                 CGroupBlockIODeviceWeight *w;
c2dfb7
 
c2dfb7
-                                /* FIXME: no way to reset this list */
c2dfb7
                                 LIST_FOREACH(device_weights, w, c->blockio_device_weights)
c2dfb7
                                         cgroup_apply_blkio_device_weight(u, w->path, w->weight);
c2dfb7
                         }
c2dfb7
                 }
c2dfb7
 
c2dfb7
-                /* Apply limits and free ones without config. */
c2dfb7
                 if (has_io) {
c2dfb7
                         CGroupIODeviceLimit *l;
c2dfb7
 
c2dfb7
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
c2dfb7
index da10575394..f7365b4c46 100644
c2dfb7
--- a/src/core/cgroup.h
c2dfb7
+++ b/src/core/cgroup.h
c2dfb7
@@ -13,6 +13,7 @@ typedef struct CGroupContext CGroupContext;
c2dfb7
 typedef struct CGroupDeviceAllow CGroupDeviceAllow;
c2dfb7
 typedef struct CGroupIODeviceWeight CGroupIODeviceWeight;
c2dfb7
 typedef struct CGroupIODeviceLimit CGroupIODeviceLimit;
c2dfb7
+typedef struct CGroupIODeviceLatency CGroupIODeviceLatency;
c2dfb7
 typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight;
c2dfb7
 typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth;
c2dfb7
 
c2dfb7
@@ -52,6 +53,12 @@ struct CGroupIODeviceLimit {
c2dfb7
         uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX];
c2dfb7
 };
c2dfb7
 
c2dfb7
+struct CGroupIODeviceLatency {
c2dfb7
+        LIST_FIELDS(CGroupIODeviceLatency, device_latencies);
c2dfb7
+        char *path;
c2dfb7
+        usec_t target_usec;
c2dfb7
+};
c2dfb7
+
c2dfb7
 struct CGroupBlockIODeviceWeight {
c2dfb7
         LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights);
c2dfb7
         char *path;
c2dfb7
@@ -85,6 +92,7 @@ struct CGroupContext {
c2dfb7
         uint64_t startup_io_weight;
c2dfb7
         LIST_HEAD(CGroupIODeviceWeight, io_device_weights);
c2dfb7
         LIST_HEAD(CGroupIODeviceLimit, io_device_limits);
c2dfb7
+        LIST_HEAD(CGroupIODeviceLatency, io_device_latencies);
c2dfb7
 
c2dfb7
         uint64_t memory_low;
c2dfb7
         uint64_t memory_high;
c2dfb7
@@ -137,6 +145,7 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c);
c2dfb7
 void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a);
c2dfb7
 void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w);
c2dfb7
 void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l);
c2dfb7
+void cgroup_context_free_io_device_latency(CGroupContext *c, CGroupIODeviceLatency *l);
c2dfb7
 void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w);
c2dfb7
 void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
c2dfb7
 
c2dfb7
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
c2dfb7
index 30d4e83932..a1d3014d61 100644
c2dfb7
--- a/src/core/dbus-cgroup.c
c2dfb7
+++ b/src/core/dbus-cgroup.c
c2dfb7
@@ -140,6 +140,36 @@ static int property_get_io_device_limits(
c2dfb7
         return sd_bus_message_close_container(reply);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static int property_get_io_device_latency(
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
+        CGroupContext *c = userdata;
c2dfb7
+        CGroupIODeviceLatency *l;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(bus);
c2dfb7
+        assert(reply);
c2dfb7
+        assert(c);
c2dfb7
+
c2dfb7
+        r = sd_bus_message_open_container(reply, 'a', "(st)");
c2dfb7
+        if (r < 0)
c2dfb7
+                return r;
c2dfb7
+
c2dfb7
+        LIST_FOREACH(device_latencies, l, c->io_device_latencies) {
c2dfb7
+                r = sd_bus_message_append(reply, "(st)", l->path, l->target_usec);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return r;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        return sd_bus_message_close_container(reply);
c2dfb7
+}
c2dfb7
+
c2dfb7
 static int property_get_blockio_device_weight(
c2dfb7
                 sd_bus *bus,
c2dfb7
                 const char *path,
c2dfb7
@@ -314,6 +344,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
c2dfb7
         SD_BUS_PROPERTY("IOWriteBandwidthMax", "a(st)", property_get_io_device_limits, 0, 0),
c2dfb7
         SD_BUS_PROPERTY("IOReadIOPSMax", "a(st)", property_get_io_device_limits, 0, 0),
c2dfb7
         SD_BUS_PROPERTY("IOWriteIOPSMax", "a(st)", property_get_io_device_limits, 0, 0),
c2dfb7
+        SD_BUS_PROPERTY("IODeviceLatencyTargetUSec", "a(st)", property_get_io_device_latency, 0, 0),
c2dfb7
         SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0),
c2dfb7
         SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0),
c2dfb7
         SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0),
c2dfb7
@@ -898,6 +929,86 @@ int bus_cgroup_set_property(
c2dfb7
 
c2dfb7
                 return 1;
c2dfb7
 
c2dfb7
+        } else if (streq(name, "IODeviceLatencyTargetUSec")) {
c2dfb7
+                const char *path;
c2dfb7
+                uint64_t target;
c2dfb7
+                unsigned n = 0;
c2dfb7
+
c2dfb7
+                r = sd_bus_message_enter_container(message, 'a', "(st)");
c2dfb7
+                if (r < 0)
c2dfb7
+                        return r;
c2dfb7
+
c2dfb7
+                while ((r = sd_bus_message_read(message, "(st)", &path, &target)) > 0) {
c2dfb7
+
c2dfb7
+                        if (!path_is_normalized(path))
c2dfb7
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path '%s' specified in %s= is not normalized.", name, path);
c2dfb7
+
c2dfb7
+                        if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c2dfb7
+                                CGroupIODeviceLatency *a = NULL, *b;
c2dfb7
+
c2dfb7
+                                LIST_FOREACH(device_latencies, b, c->io_device_latencies) {
c2dfb7
+                                        if (path_equal(b->path, path)) {
c2dfb7
+                                                a = b;
c2dfb7
+                                                break;
c2dfb7
+                                        }
c2dfb7
+                                }
c2dfb7
+
c2dfb7
+                                if (!a) {
c2dfb7
+                                        a = new0(CGroupIODeviceLatency, 1);
c2dfb7
+                                        if (!a)
c2dfb7
+                                                return -ENOMEM;
c2dfb7
+
c2dfb7
+                                        a->path = strdup(path);
c2dfb7
+                                        if (!a->path) {
c2dfb7
+                                                free(a);
c2dfb7
+                                                return -ENOMEM;
c2dfb7
+                                        }
c2dfb7
+                                        LIST_PREPEND(device_latencies, c->io_device_latencies, a);
c2dfb7
+                                }
c2dfb7
+
c2dfb7
+                                a->target_usec = target;
c2dfb7
+                        }
c2dfb7
+
c2dfb7
+                        n++;
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                r = sd_bus_message_exit_container(message);
c2dfb7
+                if (r < 0)
c2dfb7
+                        return r;
c2dfb7
+
c2dfb7
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c2dfb7
+                        _cleanup_free_ char *buf = NULL;
c2dfb7
+                        _cleanup_fclose_ FILE *f = NULL;
c2dfb7
+                        char ts[FORMAT_TIMESPAN_MAX];
c2dfb7
+                        CGroupIODeviceLatency *a;
c2dfb7
+                        size_t size = 0;
c2dfb7
+
c2dfb7
+                        if (n == 0) {
c2dfb7
+                                while (c->io_device_latencies)
c2dfb7
+                                        cgroup_context_free_io_device_latency(c, c->io_device_latencies);
c2dfb7
+                        }
c2dfb7
+
c2dfb7
+                        unit_invalidate_cgroup(u, CGROUP_MASK_IO);
c2dfb7
+
c2dfb7
+                        f = open_memstream(&buf, &size);
c2dfb7
+                        if (!f)
c2dfb7
+                                return -ENOMEM;
c2dfb7
+
c2dfb7
+                        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
c2dfb7
+
c2dfb7
+                        fputs("IODeviceLatencyTargetSec=\n", f);
c2dfb7
+                        LIST_FOREACH(device_latencies, a, c->io_device_latencies)
c2dfb7
+                                fprintf(f, "IODeviceLatencyTargetSec=%s %s\n",
c2dfb7
+                                        a->path, format_timespan(ts, sizeof(ts), a->target_usec, 1));
c2dfb7
+
c2dfb7
+                        r = fflush_and_check(f);
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return r;
c2dfb7
+                        unit_write_setting(u, flags, name, buf);
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                return 1;
c2dfb7
+
c2dfb7
         } else if (STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
c2dfb7
                 const char *path;
c2dfb7
                 bool read = true;
c2dfb7
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
c2dfb7
index 8883818ff2..23879c001f 100644
c2dfb7
--- a/src/core/load-fragment-gperf.gperf.m4
c2dfb7
+++ b/src/core/load-fragment-gperf.gperf.m4
c2dfb7
@@ -185,6 +185,7 @@ $1.IOReadBandwidthMax,           config_parse_io_limit,              0,
c2dfb7
 $1.IOWriteBandwidthMax,          config_parse_io_limit,              0,                             offsetof($1, cgroup_context)
c2dfb7
 $1.IOReadIOPSMax,                config_parse_io_limit,              0,                             offsetof($1, cgroup_context)
c2dfb7
 $1.IOWriteIOPSMax,               config_parse_io_limit,              0,                             offsetof($1, cgroup_context)
c2dfb7
+$1.IODeviceLatencyTargetSec,     config_parse_io_device_latency,     0,                             offsetof($1, cgroup_context)
c2dfb7
 $1.BlockIOAccounting,            config_parse_bool,                  0,                             offsetof($1, cgroup_context.blockio_accounting)
c2dfb7
 $1.BlockIOWeight,                config_parse_blockio_weight,        0,                             offsetof($1, cgroup_context.blockio_weight)
c2dfb7
 $1.StartupBlockIOWeight,         config_parse_blockio_weight,        0,                             offsetof($1, cgroup_context.startup_blockio_weight)
c2dfb7
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
c2dfb7
index 9b2724307d..1e22013b75 100644
c2dfb7
--- a/src/core/load-fragment.c
c2dfb7
+++ b/src/core/load-fragment.c
c2dfb7
@@ -3383,6 +3383,77 @@ int config_parse_io_device_weight(
c2dfb7
         return 0;
c2dfb7
 }
c2dfb7
 
c2dfb7
+int config_parse_io_device_latency(
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
+        _cleanup_free_ char *path = NULL, *resolved = NULL;
c2dfb7
+        CGroupIODeviceLatency *l;
c2dfb7
+        CGroupContext *c = data;
c2dfb7
+        const char *p = rvalue;
c2dfb7
+        usec_t usec;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(filename);
c2dfb7
+        assert(lvalue);
c2dfb7
+        assert(rvalue);
c2dfb7
+
c2dfb7
+        if (isempty(rvalue)) {
c2dfb7
+                while (c->io_device_latencies)
c2dfb7
+                        cgroup_context_free_io_device_latency(c, c->io_device_latencies);
c2dfb7
+
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = extract_first_word(&p, &path, NULL, EXTRACT_QUOTES);
c2dfb7
+        if (r == -ENOMEM)
c2dfb7
+                return log_oom();
c2dfb7
+        if (r < 0) {
c2dfb7
+                log_syntax(unit, LOG_WARNING, filename, line, r,
c2dfb7
+                           "Invalid syntax, ignoring: %s", rvalue);
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+        if (r == 0 || isempty(p)) {
c2dfb7
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
c2dfb7
+                           "Failed to extract device path and latency from '%s', ignoring.", rvalue);
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = unit_full_printf(userdata, path, &resolved);
c2dfb7
+        if (r < 0) {
c2dfb7
+                log_syntax(unit, LOG_WARNING, filename, line, r,
c2dfb7
+                           "Failed to resolve unit specifiers in '%s', ignoring: %m", path);
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
c2dfb7
+        if (r < 0)
c2dfb7
+                return 0;
c2dfb7
+
c2dfb7
+        if (parse_sec(p, &usec) < 0) {
c2dfb7
+                log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", p);
c2dfb7
+                return 0;
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        l = new0(CGroupIODeviceLatency, 1);
c2dfb7
+        if (!l)
c2dfb7
+                return log_oom();
c2dfb7
+
c2dfb7
+        l->path = TAKE_PTR(resolved);
c2dfb7
+        l->target_usec = usec;
c2dfb7
+
c2dfb7
+        LIST_PREPEND(device_latencies, c->io_device_latencies, l);
c2dfb7
+        return 0;
c2dfb7
+}
c2dfb7
+
c2dfb7
 int config_parse_io_limit(
c2dfb7
                 const char *unit,
c2dfb7
                 const char *filename,
c2dfb7
@@ -4572,6 +4643,7 @@ void unit_dump_config_items(FILE *f) {
c2dfb7
                 { config_parse_device_policy,         "POLICY" },
c2dfb7
                 { config_parse_io_limit,              "LIMIT" },
c2dfb7
                 { config_parse_io_device_weight,      "DEVICEWEIGHT" },
c2dfb7
+                { config_parse_io_device_latency,     "DEVICELATENCY" },
c2dfb7
                 { config_parse_blockio_bandwidth,     "BANDWIDTH" },
c2dfb7
                 { config_parse_blockio_weight,        "WEIGHT" },
c2dfb7
                 { config_parse_blockio_device_weight, "DEVICEWEIGHT" },
c2dfb7
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
c2dfb7
index 424fa478a7..65a94d53cc 100644
c2dfb7
--- a/src/core/load-fragment.h
c2dfb7
+++ b/src/core/load-fragment.h
c2dfb7
@@ -68,6 +68,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tasks_max);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_delegate);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_device_policy);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_device_allow);
c2dfb7
+CONFIG_PARSER_PROTOTYPE(config_parse_io_device_latency);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_io_device_weight);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_io_limit);
c2dfb7
 CONFIG_PARSER_PROTOTYPE(config_parse_blockio_weight);
c2dfb7
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
c2dfb7
index e0b2cfb170..3c1ecf2027 100644
c2dfb7
--- a/src/shared/bus-unit-util.c
c2dfb7
+++ b/src/shared/bus-unit-util.c
c2dfb7
@@ -566,6 +566,37 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
c2dfb7
                 return 1;
c2dfb7
         }
c2dfb7
 
c2dfb7
+        if (streq(field, "IODeviceLatencyTargetSec")) {
c2dfb7
+                const char *field_usec = "IODeviceLatencyTargetUSec";
c2dfb7
+
c2dfb7
+                if (isempty(eq))
c2dfb7
+                        r = sd_bus_message_append(m, "(sv)", field_usec, "a(st)", USEC_INFINITY);
c2dfb7
+                else {
c2dfb7
+                        const char *path, *target, *e;
c2dfb7
+                        usec_t usec;
c2dfb7
+
c2dfb7
+                        e = strchr(eq, ' ');
c2dfb7
+                        if (!e) {
c2dfb7
+                                log_error("Failed to parse %s value %s.", field, eq);
c2dfb7
+                                return -EINVAL;
c2dfb7
+                        }
c2dfb7
+
c2dfb7
+                        path = strndupa(eq, e - eq);
c2dfb7
+                        target = e+1;
c2dfb7
+
c2dfb7
+                        r = parse_sec(target, &usec);
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return log_error_errno(r, "Failed to parse %s value %s: %m", field, target);
c2dfb7
+
c2dfb7
+                        r = sd_bus_message_append(m, "(sv)", field_usec, "a(st)", 1, path, usec);
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                if (r < 0)
c2dfb7
+                        return bus_log_create_error(r);
c2dfb7
+
c2dfb7
+                return 1;
c2dfb7
+        }
c2dfb7
+
c2dfb7
         if (STR_IN_SET(field, "IPAddressAllow", "IPAddressDeny")) {
c2dfb7
                 unsigned char prefixlen;
c2dfb7
                 union in_addr_union prefix = {};
c2dfb7
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
c2dfb7
index a3074bc5e3..559e49f104 100644
c2dfb7
--- a/src/systemctl/systemctl.c
c2dfb7
+++ b/src/systemctl/systemctl.c
c2dfb7
@@ -4875,6 +4875,28 @@ static int print_property(const char *name, sd_bus_message *m, bool value, bool
c2dfb7
 
c2dfb7
                         return 1;
c2dfb7
 
c2dfb7
+                }  else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN &&
c2dfb7
+                            streq(name, "IODeviceLatencyTargetUSec")) {
c2dfb7
+                        char ts[FORMAT_TIMESPAN_MAX];
c2dfb7
+                        const char *path;
c2dfb7
+                        uint64_t target;
c2dfb7
+
c2dfb7
+                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)");
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return bus_log_parse_error(r);
c2dfb7
+
c2dfb7
+                        while ((r = sd_bus_message_read(m, "(st)", &path, &target)) > 0)
c2dfb7
+                                print_prop(name, "%s %s", strna(path),
c2dfb7
+                                           format_timespan(ts, sizeof(ts), target, 1));
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return bus_log_parse_error(r);
c2dfb7
+
c2dfb7
+                        r = sd_bus_message_exit_container(m);
c2dfb7
+                        if (r < 0)
c2dfb7
+                                return bus_log_parse_error(r);
c2dfb7
+
c2dfb7
+                        return 1;
c2dfb7
+
c2dfb7
                 } else if (contents[0] == SD_BUS_TYPE_BYTE && streq(name, "StandardInputData")) {
c2dfb7
                         _cleanup_free_ char *h = NULL;
c2dfb7
                         const void *p;