]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
croup: expose IO accounting data per unit
authorLennart Poettering <lennart@poettering.net>
Fri, 22 Mar 2019 11:16:03 +0000 (12:16 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 12 Apr 2019 12:25:44 +0000 (14:25 +0200)
This was the last kind of accounting still not exposed on for each unit.
Let's fix that.

Note that this is a relatively simplistic approach: we don't expose
per-device stats, but sum them all up, much like cgtop does. This kind
of metric is probably the most interesting for most usecases, and covers
the "systemctl status" output best. If we want per-device stats one day
we can of course always add that eventually.

src/core/cgroup.c
src/core/cgroup.h
src/core/dbus-unit.c
src/core/unit.c
src/core/unit.h

index 5bf6224e3111b019ca87cef7ee58c70af4be2f35..2bfa8df0cb4403a2927dadbe7ae55a1045b4a366 100644 (file)
@@ -3178,6 +3178,127 @@ int unit_get_ip_accounting(
         return r;
 }
 
+static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_ACCOUNTING_METRIC_MAX]) {
+        static const char *const field_names[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
+                [CGROUP_IO_READ_BYTES]       = "rbytes=",
+                [CGROUP_IO_WRITE_BYTES]      = "wbytes=",
+                [CGROUP_IO_READ_OPERATIONS]  = "rios=",
+                [CGROUP_IO_WRITE_OPERATIONS] = "wios=",
+        };
+        uint64_t acc[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {};
+        _cleanup_free_ char *path = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(u);
+
+        if (!u->cgroup_path)
+                return -ENODATA;
+
+        if (unit_has_host_root_cgroup(u))
+                return -ENODATA; /* TODO: return useful data for the top-level cgroup */
+
+        r = cg_all_unified();
+        if (r < 0)
+                return r;
+        if (r == 0) /* TODO: support cgroupv1 */
+                return -ENODATA;
+
+        if (!FLAGS_SET(u->cgroup_realized_mask, CGROUP_MASK_IO))
+                return -ENODATA;
+
+        r = cg_get_path("io", u->cgroup_path, "io.stat", &path);
+        if (r < 0)
+                return r;
+
+        f = fopen(path, "re");
+        if (!f)
+                return -errno;
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                const char *p;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                p = line;
+                p += strcspn(p, WHITESPACE); /* Skip over device major/minor */
+                p += strspn(p, WHITESPACE);  /* Skip over following whitespace */
+
+                for (;;) {
+                        _cleanup_free_ char *word = NULL;
+
+                        r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                break;
+
+                        for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+                                const char *x;
+
+                                x = startswith(word, field_names[i]);
+                                if (x) {
+                                        uint64_t w;
+
+                                        r = safe_atou64(x, &w);
+                                        if (r < 0)
+                                                return r;
+
+                                        /* Sum up the stats of all devices */
+                                        acc[i] += w;
+                                        break;
+                                }
+                        }
+                }
+        }
+
+        memcpy(ret, acc, sizeof(acc));
+        return 0;
+}
+
+int unit_get_io_accounting(
+                Unit *u,
+                CGroupIOAccountingMetric metric,
+                bool allow_cache,
+                uint64_t *ret) {
+
+        uint64_t raw[_CGROUP_IO_ACCOUNTING_METRIC_MAX];
+        int r;
+
+        /* Retrieve an IO account parameter. This will subtract the counter when the unit was started. */
+
+        if (!UNIT_CGROUP_BOOL(u, io_accounting))
+                return -ENODATA;
+
+        if (allow_cache && u->io_accounting_last[metric] != UINT64_MAX)
+                goto done;
+
+        r = unit_get_io_accounting_raw(u, raw);
+        if (r == -ENODATA && u->io_accounting_last[metric] != UINT64_MAX)
+                goto done;
+        if (r < 0)
+                return r;
+
+        for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) {
+                /* Saturated subtraction */
+                if (raw[i] > u->io_accounting_base[i])
+                        u->io_accounting_last[i] = raw[i] - u->io_accounting_base[i];
+                else
+                        u->io_accounting_last[i] = 0;
+        }
+
+done:
+        if (ret)
+                *ret = u->io_accounting_last[metric];
+
+        return 0;
+}
+
 int unit_reset_cpu_accounting(Unit *u) {
         int r;
 
@@ -3210,15 +3331,33 @@ int unit_reset_ip_accounting(Unit *u) {
         return r < 0 ? r : q;
 }
 
+int unit_reset_io_accounting(Unit *u) {
+        int r;
+
+        assert(u);
+
+        for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++)
+                u->io_accounting_last[i] = UINT64_MAX;
+
+        r = unit_get_io_accounting_raw(u, u->io_accounting_base);
+        if (r < 0) {
+                zero(u->io_accounting_base);
+                return r;
+        }
+
+        return 0;
+}
+
 int unit_reset_accounting(Unit *u) {
-        int r, q;
+        int r, q, v;
 
         assert(u);
 
         r = unit_reset_cpu_accounting(u);
-        q = unit_reset_ip_accounting(u);
+        q = unit_reset_io_accounting(u);
+        v = unit_reset_ip_accounting(u);
 
-        return r < 0 ? r : q;
+        return r < 0 ? r : q < 0 ? q : v;
 }
 
 void unit_invalidate_cgroup(Unit *u, CGroupMask m) {
index bfb2fa0a53b520254097838bc2b8ac2af0148fd9..ad04be30e91a6d4ebc6a6f847c8696d730412d50 100644 (file)
@@ -135,6 +135,16 @@ typedef enum CGroupIPAccountingMetric {
         _CGROUP_IP_ACCOUNTING_METRIC_INVALID = -1,
 } CGroupIPAccountingMetric;
 
+/* Used when querying IO accounting data */
+typedef enum CGroupIOAccountingMetric {
+        CGROUP_IO_READ_BYTES,
+        CGROUP_IO_WRITE_BYTES,
+        CGROUP_IO_READ_OPERATIONS,
+        CGROUP_IO_WRITE_OPERATIONS,
+        _CGROUP_IO_ACCOUNTING_METRIC_MAX,
+        _CGROUP_IO_ACCOUNTING_METRIC_INVALID = -1,
+} CGroupIOAccountingMetric;
+
 typedef struct Unit Unit;
 typedef struct Manager Manager;
 
@@ -200,10 +210,12 @@ int unit_synthesize_cgroup_empty_event(Unit *u);
 int unit_get_memory_current(Unit *u, uint64_t *ret);
 int unit_get_tasks_current(Unit *u, uint64_t *ret);
 int unit_get_cpu_usage(Unit *u, nsec_t *ret);
+int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret);
 int unit_get_ip_accounting(Unit *u, CGroupIPAccountingMetric metric, uint64_t *ret);
 
 int unit_reset_cpu_accounting(Unit *u);
 int unit_reset_ip_accounting(Unit *u);
+int unit_reset_io_accounting(Unit *u);
 int unit_reset_accounting(Unit *u);
 
 #define UNIT_CGROUP_BOOL(u, name)                       \
index d34833f4ffee5688d3b69c406abbd685301ac7b6..1d73e0d46dcc99d393a6c5cbcd8758dc4d271505 100644 (file)
@@ -1051,6 +1051,36 @@ static int property_get_ip_counter(
         return sd_bus_message_append(reply, "t", value);
 }
 
+static int property_get_io_counter(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        static const char *const table[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
+                [CGROUP_IO_READ_BYTES]       = "IOReadBytes",
+                [CGROUP_IO_WRITE_BYTES]      = "IOWriteBytes",
+                [CGROUP_IO_READ_OPERATIONS]  = "IOReadOperations",
+                [CGROUP_IO_WRITE_OPERATIONS] = "IOWriteOperations",
+        };
+
+        uint64_t value = UINT64_MAX;
+        Unit *u = userdata;
+        ssize_t metric;
+
+        assert(bus);
+        assert(reply);
+        assert(property);
+        assert(u);
+
+        assert_se((metric = string_table_lookup(table, ELEMENTSOF(table), property)) >= 0);
+        (void) unit_get_io_accounting(u, metric, false, &value);
+        return sd_bus_message_append(reply, "t", value);
+}
+
 int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
 
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
@@ -1174,6 +1204,10 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = {
         SD_BUS_PROPERTY("IPIngressPackets", "t", property_get_ip_counter, 0, 0),
         SD_BUS_PROPERTY("IPEgressBytes", "t", property_get_ip_counter, 0, 0),
         SD_BUS_PROPERTY("IPEgressPackets", "t", property_get_ip_counter, 0, 0),
+        SD_BUS_PROPERTY("IOReadBytes", "t", property_get_io_counter, 0, 0),
+        SD_BUS_PROPERTY("IOReadOperations", "t", property_get_io_counter, 0, 0),
+        SD_BUS_PROPERTY("IOWriteBytes", "t", property_get_io_counter, 0, 0),
+        SD_BUS_PROPERTY("IOWriteOperations", "t", property_get_io_counter, 0, 0),
         SD_BUS_METHOD("GetProcesses", NULL, "a(sus)", bus_unit_method_get_processes, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("AttachProcesses", "sau", NULL, bus_unit_method_attach_processes, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_VTABLE_END
index bbc27243a9990c46361be3487b30acf28b663c1e..3225ad68e8f83b8d81c4b1cd8733f976adbd4c3c 100644 (file)
@@ -113,6 +113,9 @@ Unit *unit_new(Manager *m, size_t size) {
         RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst);
         RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16);
 
+        for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++)
+                u->io_accounting_last[i] = UINT64_MAX;
+
         return u;
 }
 
@@ -3202,6 +3205,20 @@ static const char *const ip_accounting_metric_field[_CGROUP_IP_ACCOUNTING_METRIC
         [CGROUP_IP_EGRESS_PACKETS] = "ip-accounting-egress-packets",
 };
 
+static const char *const io_accounting_metric_field_base[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
+        [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-base",
+        [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-base",
+        [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-base",
+        [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-base",
+};
+
+static const char *const io_accounting_metric_field_last[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = {
+        [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-last",
+        [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-last",
+        [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-last",
+        [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-last",
+};
+
 int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
         CGroupIPAccountingMetric m;
         int r;
@@ -3248,6 +3265,13 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
         if (u->oom_kill_last > 0)
                 (void) serialize_item_format(f, "oom-kill-last", "%" PRIu64, u->oom_kill_last);
 
+        for (CGroupIOAccountingMetric im = 0; im < _CGROUP_IO_ACCOUNTING_METRIC_MAX; im++) {
+                (void) serialize_item_format(f, io_accounting_metric_field_base[im], "%" PRIu64, u->io_accounting_base[im]);
+
+                if (u->io_accounting_last[im] != UINT64_MAX)
+                        (void) serialize_item_format(f, io_accounting_metric_field_last[im], "%" PRIu64, u->io_accounting_last[im]);
+        }
+
         if (u->cgroup_path)
                 (void) serialize_item(f, "cgroup", u->cgroup_path);
 
@@ -3588,6 +3612,30 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
                         continue;
                 }
 
+                m = string_table_lookup(io_accounting_metric_field_base, ELEMENTSOF(io_accounting_metric_field_base), l);
+                if (m >= 0) {
+                        uint64_t c;
+
+                        r = safe_atou64(v, &c);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse IO accounting base value %s, ignoring.", v);
+                        else
+                                u->io_accounting_base[m] = c;
+                        continue;
+                }
+
+                m = string_table_lookup(io_accounting_metric_field_last, ELEMENTSOF(io_accounting_metric_field_last), l);
+                if (m >= 0) {
+                        uint64_t c;
+
+                        r = safe_atou64(v, &c);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse IO accounting last value %s, ignoring.", v);
+                        else
+                                u->io_accounting_last[m] = c;
+                        continue;
+                }
+
                 if (unit_can_serialize(u)) {
                         r = exec_runtime_deserialize_compat(u, l, v, fds);
                         if (r < 0) {
index f730f6eee490e9d2a68663473f51edebd06686cb..150ca7be721483eaec42abec6b2e61b1b5b8ba7f 100644 (file)
@@ -252,6 +252,10 @@ typedef struct Unit {
         /* The  current counter of the oom_kill field in the memory.events cgroup attribute */
         uint64_t oom_kill_last;
 
+        /* Where the io.stat data was at the time the unit was started */
+        uint64_t io_accounting_base[_CGROUP_IO_ACCOUNTING_METRIC_MAX];
+        uint64_t io_accounting_last[_CGROUP_IO_ACCOUNTING_METRIC_MAX]; /* the most recently read value */
+
         /* Counterparts in the cgroup filesystem */
         char *cgroup_path;
         CGroupMask cgroup_realized_mask;           /* In which hierarchies does this unit's cgroup exist? (only relevant on cgroup v1) */