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.
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;
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) {
_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;
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) \
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;
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
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;
}
[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;
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);
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) {
/* 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) */