diff --git a/tuned.spec b/tuned.spec index b87a9fb..6ffc954 100644 --- a/tuned.spec +++ b/tuned.spec @@ -12,6 +12,7 @@ Requires(preun): systemd Requires(postun): systemd Requires: python-decorator, dbus-python, pygobject3-base, python-pyudev Requires: virt-what, python-configobj, ethtool, gawk, kernel-tools, hdparm +Requires: util-linux %description The tuned package contains a daemon that tunes system settings dynamically. diff --git a/tuned/plugins/plugin_scheduler.py b/tuned/plugins/plugin_scheduler.py new file mode 100644 index 0000000..381670d --- /dev/null +++ b/tuned/plugins/plugin_scheduler.py @@ -0,0 +1,170 @@ +import base +from decorators import * +import tuned.logs +import re +from subprocess import * +from tuned.utils.commands import commands + +log = tuned.logs.get() + +class SchedulerPlugin(base.Plugin): + """ + Plugin for tuning of scheduler. Currently it can control scheduling + priorities of system threads (it is substitution for the rtctl tool). + """ + + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self._has_dynamic_options = True + self._cmd = commands() + + def _scheduler_storage_key(self, instance): + return "%s/options" % instance.name + + def _instance_init(self, instance): + instance._has_dynamic_tuning = False + instance._has_static_tuning = True + + # FIXME: do we want to do this here? + # recover original values in case of crash + instance._scheduler_original = self._storage.get(self._scheduler_storage_key(instance), {}) + if len(instance._scheduler_original) > 0: + log.info("recovering scheduling settings from previous run") + self._instance_unapply_static(instance) + instance._scheduler_original = {} + self._storage.unset(self._scheduler_storage_key(instance)) + + instance._scheduler = instance.options + + def _instance_cleanup(self, instance): + pass +# self._storage.unset(self._scheduler_storage_key(instance)) + + def get_processes(self): + (rc, out) = self._cmd.execute(["ps", "-eopid,cmd", "--no-headers"]) + if rc != 0 or len(out) <= 0: + return None + return dict(map(lambda (pid, cmd): (pid.lstrip(), cmd.lstrip()), + filter(lambda i: len(i) == 2, map(lambda s: s.split(None, 1), out.split("\n"))))) + + def _sched2abbrev(sched): + if (sched == "SCHED_OTHER"): + return + elif (sched == "SCHED_FIFO"): + return + elif (sched == "SCHED_RR"): + return + elif (sched == "SCHED_BATCH"): + return + elif (sched == "SCHED_IDLE"): + return + else: + return None + + def _parse_val(self, val): + v = val.split(":", 1) + if len(v) == 2: + return v[1].strip() + else: + return None + + def _get_rt(self, pid): + (rc, out) = self._cmd.execute(["chrt", "-p", str(pid)]) + if rc != 0: + return None + vals = out.split("\n", 1) + if len(vals) > 1: + sched = self._parse_val(vals[0]) + prio = self._parse_val(vals[1]) + else: + sched = None + prio = None + log.debug("read scheduler policy '%s' and priority '%s' for pid '%s'" % (sched, prio, pid)) + return (sched, prio) + + def _get_affinity(self, pid): + (rc, out) = self._cmd.execute(["taskset", "-p", str(pid)]) + if rc != 0: + return None + v = self._parse_val(out.split("\n", 1)[0]) + log.debug("read affinity '%s' for pid '%s'" % (v, pid)) + return v + + def _schedcfg2param(self, sched): + if sched == "f": + return "-f" + elif sched == "b": + return "-b" + elif sched == "r": + return "-r" + elif sched == "o": + return "-o" + else: + return "" + + def _sched2param(self, sched): + if sched == "SCHED_FIFO": + return "-f" + elif sched == "SCHED_BATCH": + return "-b" + elif sched == "SCHED_RR": + return "-r" + elif sched == "SCHED_OTHER": + return "-o" + elif sched == "SCHED_IDLE": + return "-i" + else: + return "" + + def _set_rt(self, pid, sched, prio): + if pid is None or prio is None: + return + if sched is not None and len(sched) > 0: + schedl = [sched] + log.debug("setting scheduler policy to '%s' for PID '%s'" % (sched, pid)) + else: + schedl = [] + log.debug("setting scheduler priority to '%s' for PID '%s'" % (prio, pid)) + self._cmd.execute(["chrt"] + schedl + ["-p", str(prio), str(pid)]) + + def _set_affinity(self, pid, affinity): + if pid is None or affinity is None: + return + log.debug("setting affinity to '%s' for PID '%s'" % (affinity, pid)) + self._cmd.execute(["taskset", "-p", str(affinity), str(pid)]) + + def _instance_apply_static(self, instance): + ps = self.get_processes() + if ps is None: + log.error("error applying tuning, cannot get information about running processes") + return + sched_cfg = map(lambda (option, value): (option, value.split(":", 4)), instance._scheduler.items()) + buf = filter(lambda (option, vals): re.match(r"group\.", option) and len(vals) == 5, sched_cfg) + sched_cfg = sorted(buf, key=lambda (option, vals): vals[0]) + sched_all = dict() + for option, vals in sched_cfg: + try: + r = re.compile(vals[4]) + except re.error as e: + log.error("error compiling regular expression: '%s'" % str(vals[4])) + continue + processes = filter(lambda (pid, cmd): re.search(r, cmd) is not None, ps.items()) + sched = dict(map(lambda (pid, cmd): (pid, (cmd, option, vals[1], vals[2], vals[3], vals[4])), processes)) + sched_all.update(sched) + for pid, vals in sched_all.items(): + (sched, prio) = self._get_rt(pid) + affinity = self._get_affinity(pid) + if affinity is not None and sched is not None and prio is not None: + instance._scheduler_original[pid] = (vals[0], sched, prio, affinity) + self._set_rt(pid, self._schedcfg2param(vals[2]), vals[3]) + if vals[4] != "*": + self._set_affinity(pid, vals[4]) + self._storage.set("options", instance._scheduler_original) + + def _instance_unapply_static(self, instance, profile_switch = False): + ps = self.get_processes() + for pid, vals in instance._scheduler_original.iteritems(): + # if command line for the pid didn't change, it's very probably the same process + if ps[pid] == vals[0]: + self._set_rt(pid, self._sched2param(vals[1]), vals[2]) + self._set_affinity(pid, vals[3])