Mini Shell
from . import hotplug
from .decorators import *
import tuned.consts as consts
import tuned.logs
import errno
import os
log = tuned.logs.get()
# the plugin manages each IRQ as a "device" and keeps a IrqInfo object for it
class IrqInfo(object):
def __init__(self, irq):
self.irq = irq
self.device = "irq%s" % irq
self.unchangeable = False
self.original_affinity = None
class IrqPlugin(hotplug.Plugin):
r"""
`irq`::
Allows tuning of IRQ affinities, and thus re-implements functionality
already present in the `scheduler` plugin. However, this plugin offers
more flexibility, as it allows tuning of individual interrupts with
different affinities. When using the `irq` plugin, make sure to disable
IRQ processing in the `scheduler` plugin by setting its option
[option]`irq_process=false`.
The plugin handles individual IRQs as `devices`, and multiple plugin
instances can be defined, each addressing different devices/irqs.
The `device` names used by the plugin are `irq<n>`, where `<n>` is the
IRQ number. The special `device` `DEFAULT` controls values written to
`/proc/irq/default_smp_affinity`, which applies to all non-active IRQs.
===
The option [option]`affinity` controls the IRQ affinity to be set. It is
a string in "cpulist" format (such as 1,3-4). If the configured affinity
is empty, then the affinity of the respective IRQs is not touched.
===
The option [option]`mode` is a string which can either be `set` (default)
or `intersect`. In `set` mode the [option]`affinity` is always written
as configured, whereas in `intersect` mode, the new affinity will be
calculated as the intersection of the current and the configured affinity.
If that intersection is empty, the configured affinity will be used.
--
.Example moving all IRQs to CPU0, except irq16, which is directed to CPU2
====
----
[irq_special]
type=irq
devices=irq16
affinity=2
[irq]
affinity=0
----
====
"""
def __init__(self, monitor_repository, storage_factory, hardware_inventory, device_matcher, device_matcher_udev, plugin_instance_factory, global_cfg, variables):
super(IrqPlugin, self).__init__(monitor_repository, storage_factory, hardware_inventory, device_matcher, device_matcher_udev, plugin_instance_factory, global_cfg, variables)
self._irqs = {}
#
# plugin-level methods: devices and plugin options
#
def _init_devices(self):
"""Read /proc/irq to collect devices
"""
self._devices_supported = True
self._free_devices = set()
self._assigned_devices = set()
for i in os.listdir("/proc/irq"):
p = os.path.join("/proc/irq", i)
if os.path.isdir(p) and i.isdigit():
info = IrqInfo(i)
self._irqs[i] = info
self._free_devices.add(info.device)
# add the virtual device for default_smp_affinity
default_info = IrqInfo("DEFAULT")
default_info.device = "DEFAULT"
self._irqs["DEFAULT"] = default_info
self._free_devices.add(default_info.device)
@classmethod
def _get_config_options(cls):
return {
"affinity": "",
"mode": "set",
}
#
# instance-level methods: implement the Instance interface
#
def _instance_init(self, instance):
instance._has_static_tuning = True
instance._has_dynamic_tuning = False
affinity = self._variables.expand(instance.options.get("affinity"))
affinity_list = self._cmd.cpulist_unpack(affinity)
if len(affinity.strip()) == 0:
# empty affinity in profile -> assume it's intentional
log.info("Instance '%s' configured with empty affinity. Deactivating." % instance.name)
instance._active = False
elif len(affinity_list) == 0:
# non-empty affinity string evaluates to empty list -> assume parse error
log.error("Instance '%s' with invalid affinity '%s'. Deactivating." % (instance.name, affinity))
instance._active = False
mode = self._variables.expand(instance.options.get("mode"))
if mode not in ["set", "intersect"]:
log.error("Invalid operating mode '%s' for instance '%s'. Using the default 'set' instead."
% (mode, instance.name))
instance.options["mode"] = "set"
def _instance_cleanup(self, instance):
pass
def _instance_apply_static(self, instance):
log.debug("Applying IRQ affinities (%s)" % instance.name)
super(IrqPlugin, self)._instance_apply_static(instance)
def _instance_unapply_static(self, instance, rollback):
log.debug("Unapplying IRQ affinities (%s)" % instance.name)
super(IrqPlugin, self)._instance_unapply_static(instance, rollback)
def _instance_verify_static(self, instance, ignore_missing, devices):
log.debug("Verifying IRQ affinities (%s)" % instance.name)
return super(IrqPlugin, self)._instance_verify_static(instance, ignore_missing, devices)
#
# "low-level" methods to get/set irq affinities
#
def _get_irq_affinity(self, irq):
"""Get current IRQ affinity from the kernel
Args:
irq (str): IRQ number (as string) or "DEFAULT"
Returns:
affinity (set): set of all CPUs that belong to the IRQ affinity mask,
if reading of the affinity fails, an empty set is returned
"""
try:
filename = "/proc/irq/default_smp_affinity" if irq == "DEFAULT" else "/proc/irq/%s/smp_affinity" % irq
with open(filename, "r") as f:
affinity_hex = f.readline().strip()
return set(self._cmd.hex2cpulist(affinity_hex))
except (OSError, IOError) as e:
log.debug("Failed to read SMP affinity of IRQ %s: %s" % (irq, e))
return set()
def _set_irq_affinity(self, irq, affinity, restoring):
"""Set IRQ affinity in the kernel
Args:
irq (str): IRQ number (as string) or "DEFAULT"
affinity (set): affinity mask as set of CPUs
restoring (bool): are we rolling back a previous change?
Returns:
status (int): 0 on success, -2 if changing the affinity is not
supported, -1 if some other error occurs
"""
try:
affinity_hex = self._cmd.cpulist2hex(list(affinity))
log.debug("Setting SMP affinity of IRQ %s to '%s'" % (irq, affinity_hex))
filename = "/proc/irq/default_smp_affinity" if irq == "DEFAULT" else "/proc/irq/%s/smp_affinity" % irq
with open(filename, "w") as f:
f.write(affinity_hex)
return 0
except (OSError, IOError) as e:
# EIO is returned by
# kernel/irq/proc.c:write_irq_affinity() if changing
# the affinity is not supported
# (at least on kernels 3.10 and 4.18)
if hasattr(e, "errno") and e.errno == errno.EIO and not restoring:
log.debug("Setting SMP affinity of IRQ %s is not supported" % irq)
return -2
else:
log.error("Failed to set SMP affinity of IRQ %s to '%s': %s" % (irq, affinity_hex, e))
return -1
#
# "high-level" methods: apply tuning while saving original affinities
#
def _apply_irq_affinity(self, irqinfo, affinity, mode):
"""Apply IRQ affinity tuning
Args:
irqinfo (IrqInfo): IRQ that should be tuned
affinity (set): desired affinity
"""
original = self._get_irq_affinity(irqinfo.irq)
if mode == "intersect":
# intersection of affinity and original, if that is empty fall back to configured affinity
affinity = affinity & original or affinity
if irqinfo.unchangeable or affinity == original:
return
res = self._set_irq_affinity(irqinfo.irq, affinity, False)
if res == 0:
if irqinfo.original_affinity is None:
irqinfo.original_affinity = original
elif res == -2:
irqinfo.unchangeable = True
def _restore_irq_affinity(self, irqinfo):
"""Restore IRQ affinity
Args:
irqinfo (IrqInfo): IRQ that should be restored
"""
if irqinfo.unchangeable or irqinfo.original_affinity is None:
return
self._set_irq_affinity(irqinfo.irq, irqinfo.original_affinity, True)
irqinfo.original_affinity = None
def _verify_irq_affinity(self, irqinfo, affinity, mode):
"""Verify IRQ affinity tuning
Args:
irqinfo (IrqInfo): IRQ that should be verified
affinity (set): desired affinity
Returns:
status (bool): True if verification successful, False otherwise
"""
if irqinfo.unchangeable:
return True
affinity_description = "IRQ %s affinity" % irqinfo.irq
desired_affinity = affinity
desired_affinity_string = self._cmd.cpulist2string(self._cmd.cpulist_pack(list(desired_affinity)))
current_affinity = self._get_irq_affinity(irqinfo.irq)
current_affinity_string = self._cmd.cpulist2string(self._cmd.cpulist_pack(list(current_affinity)))
if mode == "intersect":
# In intersect mode, we don't use a strict comparison; it's sufficient
# if the current affinity is a subset of the desired one
desired_affinity_string = "subset of " + desired_affinity_string
if ((mode == "intersect" and current_affinity <= desired_affinity) or
(mode == "set" and current_affinity == desired_affinity)):
log.info(consts.STR_VERIFY_PROFILE_VALUE_OK
% (affinity_description, desired_affinity_string))
return True
else:
log.error(consts.STR_VERIFY_PROFILE_VALUE_FAIL
% (affinity_description, current_affinity_string, desired_affinity_string))
return False
#
# command definitions: entry to device-specific tuning
#
@command_custom("mode", per_device=False, priority=-10)
def _mode(self, enabling, value, verify, ignore_missing):
if (enabling or verify) and value is not None:
# Store the operating mode of the current instance in the plugin
# object, from where it is read by the "affinity" command.
# This works because instances are processed sequentially by the engine.
self._mode_val = value
@command_custom("affinity", per_device=True)
def _affinity(self, enabling, value, device, verify, ignore_missing):
irq = "DEFAULT" if device == "DEFAULT" else device[len("irq"):]
if irq not in self._irqs:
log.error("Unknown device: %s" % device)
return None
irqinfo = self._irqs[irq]
if verify:
affinity = set(self._cmd.cpulist_unpack(value))
return self._verify_irq_affinity(irqinfo, affinity, self._mode_val)
if enabling:
affinity = set(self._cmd.cpulist_unpack(value))
return self._apply_irq_affinity(irqinfo, affinity, self._mode_val)
else:
return self._restore_irq_affinity(irqinfo)
Zerion Mini Shell 1.0