summaryrefslogtreecommitdiffstats
path: root/src/udev/iocost
diff options
context:
space:
mode:
Diffstat (limited to 'src/udev/iocost')
-rw-r--r--src/udev/iocost/iocost.c321
-rw-r--r--src/udev/iocost/iocost.conf20
2 files changed, 341 insertions, 0 deletions
diff --git a/src/udev/iocost/iocost.c b/src/udev/iocost/iocost.c
new file mode 100644
index 0000000..2b2633e
--- /dev/null
+++ b/src/udev/iocost/iocost.c
@@ -0,0 +1,321 @@
+/* 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 "device-util.h"
+#include "devnum-util.h"
+#include "main-func.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "udev-util.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 },
+ };
+ int r;
+
+ r = config_parse(
+ NULL,
+ "/etc/udev/iocost.conf",
+ NULL,
+ "IOCost\0",
+ config_item_table_lookup,
+ items,
+ CONFIG_PARSE_WARN,
+ NULL,
+ NULL);
+ if (r < 0)
+ return r;
+
+ 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 0;
+}
+
+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, int log_level, char ***ret_solutions, const char **ret_selected) {
+ _cleanup_free_ char **s = NULL;
+ const char *value, *found;
+ int r;
+
+ assert(ret_solutions);
+ assert(ret_selected);
+
+ r = sd_device_get_property_value(device, "IOCOST_SOLUTIONS", &value);
+ if (r == -ENOENT)
+ return log_device_full_errno(device, log_level, r, "No iocost solution found for device.");
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to query solutions from device: %m");
+
+ s = strv_split(value, WHITESPACE);
+ if (!s)
+ return log_oom();
+ if (strv_isempty(s))
+ return log_device_error_errno(device, SYNTHETIC_ERRNO(EINVAL),
+ "IOCOST_SOLUTIONS exists in hwdb but is empty.");
+
+ found = strv_find(s, arg_target_solution);
+ if (found) {
+ *ret_selected = found;
+ log_device_debug(device, "Selected solution based on target solution: %s", *ret_selected);
+ } else {
+ *ret_selected = s[0];
+ log_device_debug(device, "Selected first available solution: %s", *ret_selected);
+ }
+
+ *ret_solutions = TAKE_PTR(s);
+ return 0;
+}
+
+static int query_named_solution(
+ sd_device *device,
+ const char *name,
+ const char **ret_model,
+ const char **ret_qos) {
+
+ _cleanup_free_ char *upper_name = NULL, *qos_key = NULL, *model_key = NULL;
+ const char *qos, *model;
+ int r;
+
+ assert(name);
+ assert(ret_qos);
+ assert(ret_model);
+
+ 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 QoS 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_strv_free_ char **solutions = NULL;
+ _cleanup_free_ char *qos = NULL, *model = NULL;
+ const char *qos_params, *model_params;
+ 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 = sd_device_get_devnum(device, &devnum);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Error getting devnum: %m");
+
+ if (!name) {
+ r = get_known_solutions(device, LOG_DEBUG, &solutions, &name);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+ }
+
+ r = query_named_solution(device, name, &model_params, &qos_params);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return r;
+
+ 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, 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 *selected_solution, *model_name;
+ int r;
+
+ r = sd_device_new_from_path(&device, path);
+ if (r < 0)
+ return log_error_errno(r, "Error looking up device: %m");
+
+ r = device_get_model_string(device, &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, LOG_INFO, &solutions, &selected_solution);
+ if (r == -ENOENT)
+ return 0;
+ 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, selected_solution);
+
+ STRV_FOREACH(s, solutions) {
+ const char *model, *qos;
+
+ if (query_named_solution(device, *s, &model, &qos) < 0)
+ 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;
+
+ r = parse_config();
+ if (r < 0)
+ return r;
+
+ 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 0000000..c0eeea3
--- /dev/null
+++ b/src/udev/iocost/iocost.conf
@@ -0,0 +1,20 @@
+# 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 (or a copy of it placed in
+# /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in
+# the /etc/udev/iocost.conf.d/ directory. The latter is generally recommended.
+# Defaults can be restored by simply deleting the main configuration file and
+# all drop-ins located in /etc/.
+#
+# Use 'systemd-analyze cat-config udev/iocost.conf' to display the full config.
+#
+# See iocost.conf(5) for details.
+
+[IOCost]
+#TargetSolution=naive