summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/iocost.conf.xml76
-rw-r--r--man/rules/meson.build1
-rw-r--r--rules.d/90-iocost.rules20
-rw-r--r--rules.d/meson.build1
-rw-r--r--src/udev/iocost/iocost.c334
-rw-r--r--src/udev/iocost/iocost.conf17
-rw-r--r--src/udev/meson.build3
7 files changed, 452 insertions, 0 deletions
diff --git a/man/iocost.conf.xml b/man/iocost.conf.xml
new file mode 100644
index 0000000000..be74244267
--- /dev/null
+++ b/man/iocost.conf.xml
@@ -0,0 +1,76 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="iocost.conf" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>iocost.conf</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>iocost.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>iocost.conf</refname>
+ <refpurpose>Configuration files for the iocost solution manager</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para>
+ <filename>/etc/systemd/iocost.conf</filename>
+ <filename>/etc/systemd/iocost.conf.d/*.conf</filename>
+ </para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>This file configures the behavior of <literal>iocost</literal>, a tool mostly used by
+ <citerefentry><refentrytitle>systemd-udevd</refentrytitle><manvolnum>8</manvolnum></citerefentry> rules
+ to automatically apply I/O cost solutions to <filename>/sys/fs/cgroup/io.cost.*</filename>.</para>
+
+ <para>The qos and model values are calculated based on benchmarks collected on the
+ <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">iocost-benchmark</ulink>
+ project and turned into a set of solutions that go from most to least isolated.
+ Isolation allows the system to remain responsive in face of high I/O load.
+ Which solutions are available for a device can be queried from the udev metadata attached to it. By
+ default the naive solution is used, which provides the most bandwidth.</para>
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the [IOCost] section:</para>
+
+ <variablelist class='config-directives'>
+
+ <varlistentry>
+ <term><varname>TargetSolution=</varname></term>
+
+ <listitem><para>Chooses which I/O cost solution (identified by named string) should be used
+ for the devices in this system. The known solutions can be queried from the udev metadata
+ attached to the devices. If a device does not have the specified solution, the first one
+ listed in <varname>IOCOST_SOLUTIONS</varname> is used instead.</para>
+
+ <para>E.g. <literal>TargetSolution=isolated-bandwidth</literal>.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <ulink url="https://github.com/iocost-benchmark/iocost-benchmarks">The iocost-benchmarks github project</ulink>,
+ <ulink url="https://github.com/facebookexperimental/resctl-demo/tree/main/resctl-bench/doc">The resctl-bench
+ documentation details how the values are obtained</ulink>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/rules/meson.build b/man/rules/meson.build
index cdf98eaaf0..ca3b471281 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -24,6 +24,7 @@ manpages = [
['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
['hwdb', '7', [], 'ENABLE_HWDB'],
['integritytab', '5', [], 'HAVE_LIBCRYPTSETUP'],
+ ['iocost.conf', '5', [], ''],
['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'],
['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'],
['journalctl', '1', [], ''],
diff --git a/rules.d/90-iocost.rules b/rules.d/90-iocost.rules
new file mode 100644
index 0000000000..50f778a0ae
--- /dev/null
+++ b/rules.d/90-iocost.rules
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+SUBSYSTEM!="block", GOTO="iocost_end"
+
+ENV{DEVTYPE}=="partition", GOTO="iocost_end"
+
+ACTION=="remove", GOTO="iocost_end"
+
+ENV{ID_MODEL}!="", IMPORT{builtin}="hwdb 'block::name:$env{ID_MODEL}:fwrev:$env{ID_REVISION}:'"
+
+ENV{IOCOST_SOLUTIONS}!="", RUN+="iocost apply $env{DEVNAME}"
+
+LABEL="iocost_end"
diff --git a/rules.d/meson.build b/rules.d/meson.build
index 09edd58da2..7280f5b995 100644
--- a/rules.d/meson.build
+++ b/rules.d/meson.build
@@ -28,6 +28,7 @@ rules = [
'78-sound-card.rules',
'80-net-setup-link.rules',
'81-net-dhcp.rules',
+ '90-iocost.rules',
)],
[files('80-drivers.rules'),
diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c
new file mode 100644
index 0000000000..54b50b4a8d
--- /dev/null
+++ b/src/udev/iocost/iocost.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "cgroup-util.h"
+#include "conf-parser.h"
+#include "devnum-util.h"
+#include "device-util.h"
+#include "main-func.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "verbs.h"
+
+static char *arg_target_solution = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_target_solution, freep);
+
+static int parse_config(void) {
+ static const ConfigTableItem items[] = {
+ { "IOCost", "TargetSolution", config_parse_string, 0, &arg_target_solution },
+ };
+ return config_parse(
+ NULL,
+ "/etc/udev/iocost.conf",
+ NULL,
+ "IOCost\0",
+ config_item_table_lookup,
+ items,
+ CONFIG_PARSE_WARN,
+ NULL,
+ NULL);
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...]\n\n"
+ "Set up iocost model and qos solutions for block devices\n"
+ "\nCommands:\n"
+ " apply <path> [SOLUTION] Apply solution for the device if\n"
+ " found, do nothing otherwise\n"
+ " query <path> Query the known solution for\n"
+ " the device\n"
+ "\nOptions:\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 1);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int get_known_solutions(sd_device *device, char ***ret_solutions) {
+ _cleanup_free_ char **s = NULL;
+ const char *value;
+ int r;
+
+ assert(ret_solutions);
+
+ r = sd_device_get_property_value(device, "IOCOST_SOLUTIONS", &value);
+ if (r < 0)
+ return r;
+
+ s = strv_split(value, WHITESPACE);
+ if (!s)
+ return -ENOMEM;
+
+ *ret_solutions = TAKE_PTR(s);
+
+ return 0;
+}
+
+static int choose_solution(char **solutions, const char **ret_name) {
+ assert(ret_name);
+
+ if (strv_isempty(solutions))
+ return log_error_errno(
+ SYNTHETIC_ERRNO(EINVAL), "IOCOST_SOLUTIONS exists in hwdb but is empty.");
+
+ if (arg_target_solution && strv_find(solutions, arg_target_solution)) {
+ *ret_name = arg_target_solution;
+ log_debug("Selected solution based on target solution: %s", *ret_name);
+ } else {
+ *ret_name = solutions[0];
+ log_debug("Selected first available solution: %s", *ret_name);
+ }
+
+ return 0;
+}
+
+static int query_named_solution(
+ sd_device *device,
+ const char *name,
+ const char **ret_model,
+ const char **ret_qos) {
+
+ _cleanup_strv_free_ char **solutions = NULL;
+ _cleanup_free_ char *upper_name = NULL, *qos_key = NULL, *model_key = NULL;
+ const char *qos = NULL, *model = NULL;
+ int r;
+
+ assert(ret_qos);
+ assert(ret_model);
+
+ /* If NULL is passed we query the default solution, which is the first one listed
+ * in the IOCOST_SOLUTIONS key or the one specified by the TargetSolution setting.
+ */
+ if (!name) {
+ r = get_known_solutions(device, &solutions);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No entry found for device, skipping iocost logic.");
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to query solutions from device: %m");
+
+ r = choose_solution(solutions, &name);
+ if (r < 0)
+ return r;
+ }
+
+ upper_name = strdup(name);
+ if (!upper_name)
+ return log_oom();
+
+ ascii_strupper(upper_name);
+ string_replace_char(upper_name, '-', '_');
+
+ qos_key = strjoin("IOCOST_QOS_", upper_name);
+ if (!qos_key)
+ return log_oom();
+
+ model_key = strjoin("IOCOST_MODEL_", upper_name);
+ if (!model_key)
+ return log_oom();
+
+ r = sd_device_get_property_value(device, qos_key, &qos);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", qos_key);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+ r = sd_device_get_property_value(device, model_key, &model);
+ if (r == -ENOENT)
+ return log_device_debug_errno(device, r, "No value found for key %s, skipping iocost logic.", model_key);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to obtain model for iocost solution from device: %m");
+
+ *ret_qos = qos;
+ *ret_model = model;
+
+ return 0;
+}
+
+static int apply_solution_for_path(const char *path, const char *name) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _cleanup_free_ char *qos = NULL, *model = NULL;
+ const char *qos_params = NULL, *model_params = NULL;
+ dev_t devnum;
+ int r;
+
+ r = sd_device_new_from_path(&device, path);
+ if (r < 0)
+ return log_error_errno(r, "Error looking up device: %m");
+
+ r = query_named_solution(device, name, &model_params, &qos_params);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = sd_device_get_devnum(device, &devnum);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Error getting devnum: %m");
+
+ if (asprintf(&qos, DEVNUM_FORMAT_STR " enable=1 ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), qos_params) < 0)
+ return log_oom();
+
+ if (asprintf(&model, DEVNUM_FORMAT_STR " model=linear ctrl=user %s", DEVNUM_FORMAT_VAL(devnum), model_params) < 0)
+ return log_oom();
+
+ log_debug("Applying iocost parameters to %s using solution '%s'\n"
+ "\tio.cost.qos: %s\n"
+ "\tio.cost.model: %s\n", path, name ?: "default", qos, model);
+
+ r = cg_set_attribute("io", NULL, "io.cost.qos", qos);
+ if (r < 0) {
+ log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.qos: %m");
+ return r == -ENOENT ? 0 : r;
+ }
+
+ r = cg_set_attribute("io", NULL, "io.cost.model", model);
+ if (r < 0) {
+ log_device_full_errno(device, r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, "Failed to set io.cost.model: %m");
+ return r == -ENOENT ? 0 : r;
+ }
+
+ return 0;
+}
+
+static int query_solutions_for_path(const char *path) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ _cleanup_strv_free_ char **solutions = NULL;
+ const char *default_solution = NULL;
+ const char *model_name = NULL;
+ int r;
+
+ r = sd_device_new_from_path(&device, path);
+ if (r < 0)
+ return log_error_errno(r, "Error looking up device: %m");
+
+ r = sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model_name);
+ if (r == -ENOENT) {
+ log_device_debug(device, "Missing ID_MODEL_FROM_DATABASE property, trying ID_MODEL");
+ r = sd_device_get_property_value(device, "ID_MODEL", &model_name);
+ if (r == -ENOENT) {
+ log_device_info(device, "Device model not found");
+ return 0;
+ }
+ }
+ if (r < 0)
+ return log_device_error_errno(device, r, "Model name for device %s is unknown", path);
+
+ r = get_known_solutions(device, &solutions);
+ if (r == -ENOENT) {
+ log_device_info(device, "Attribute IOCOST_SOLUTIONS missing, model not found in hwdb.");
+ return 0;
+ }
+ if (r < 0)
+ return log_device_error_errno(device, r, "Couldn't access IOCOST_SOLUTIONS for device %s, model name %s on hwdb: %m\n", path, model_name);
+
+ r = choose_solution(solutions, &default_solution);
+ if (r < 0)
+ return r;
+
+ log_info("Known solutions for %s model name: \"%s\"\n"
+ "Preferred solution: %s\n"
+ "Solution that would be applied: %s",
+ path, model_name,
+ arg_target_solution, default_solution);
+
+ STRV_FOREACH(s, solutions) {
+ const char *model = NULL, *qos = NULL;
+
+ r = query_named_solution(device, *s, &model, &qos);
+ if (r < 0 || !model || !qos)
+ continue;
+
+ log_info("%s: io.cost.qos: %s\n"
+ "%s: io.cost.model: %s", *s, qos, *s, model);
+ }
+
+ return 0;
+}
+
+static int verb_query(int argc, char *argv[], void *userdata) {
+ return query_solutions_for_path(ASSERT_PTR(argv[1]));
+}
+
+static int verb_apply(int argc, char *argv[], void *userdata) {
+ return apply_solution_for_path(
+ ASSERT_PTR(argv[1]),
+ argc > 2 ? ASSERT_PTR(argv[2]) : NULL);
+}
+
+static int iocost_main(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "query", 2, 2, 0, verb_query },
+ { "apply", 2, 3, 0, verb_apply },
+ {},
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ (void) parse_config();
+
+ if (!arg_target_solution) {
+ arg_target_solution = strdup("naive");
+ if (!arg_target_solution)
+ return log_oom();
+ }
+
+ log_debug("Target solution: %s.", arg_target_solution);
+
+ return iocost_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/udev/iocost/iocost.conf b/src/udev/iocost/iocost.conf
new file mode 100644
index 0000000000..394ea349ee
--- /dev/null
+++ b/src/udev/iocost/iocost.conf
@@ -0,0 +1,17 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Entries in this file show the compile time defaults. Local configuration
+# should be created by either modifying this file. Defaults can be restored by
+# simply deleting this file.
+#
+# Use 'systemd-analyze cat-config udev/iocost.conf' to display the full config.
+#
+# See iocost.conf(5) for details.
+
+[IOCost]
+#TargetSolution=naive
diff --git a/src/udev/meson.build b/src/udev/meson.build
index 5b44dd8d7d..081948d223 100644
--- a/src/udev/meson.build
+++ b/src/udev/meson.build
@@ -116,6 +116,7 @@ udev_progs = [['ata_id/ata_id.c'],
['cdrom_id/cdrom_id.c'],
['fido_id/fido_id.c',
'fido_id/fido_id_desc.c'],
+ ['iocost/iocost.c'],
['scsi_id/scsi_id.c',
'scsi_id/scsi_serial.c'],
['v4l_id/v4l_id.c'],
@@ -149,6 +150,8 @@ endforeach
if install_sysconfdir_samples
install_data('udev.conf',
install_dir : sysconfdir / 'udev')
+ install_data('iocost/iocost.conf',
+ install_dir : sysconfdir / 'udev')
endif
custom_target(