1
0
Fork 0
tuned/tuned/plugins/plugin_cpu.py
Jaroslav Škarvada 6bf3afa9d3 plugin_cpu: added support for sampling_down_factor and removed cpupower
The sampling rate determines how frequently the governor checks to tune the
CPU. The sampling_down_factor is a tunable that multiplies the sampling rate
when the CPU is at its highest clock frequency thereby delaying load
evaluation and improving performance. Allowed values for
sampling_down_factor are 1 to 100000.

Usage:

[cpu]
sampling_down_factor = 1

Recommended setting for jitter reduction:
sampling_down_factor = 100

Original idea of adding support for this knob to Tuned came from
Akshay Adiga <akshay.adiga@linux.vnet.ibm.com>

This patch also removes cpupower support from Tuned. It seems
that cpupower tool changes it's output format time to time, and parsing
it in Tuned becomes harder and harder. Morever the current parser in Tuned
didn't handle the recent cpupower correctly. It's much easier to just query
sysfs than supporting multiple versions of cpupower.

Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
2016-06-23 11:24:41 +02:00

300 lines
10 KiB
Python

import base
from decorators import *
import tuned.logs
from tuned.utils.commands import commands
import tuned.consts as consts
import os
import struct
import errno
log = tuned.logs.get()
# TODO: force_latency -> command
# intel_pstate
class CPULatencyPlugin(base.Plugin):
"""
Plugin for tuning CPU options. Powersaving, governor, required latency, etc.
"""
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self._has_pm_qos = True
self._has_energy_perf_bias = True
self._has_intel_pstate = False
self._min_perf_pct_save = None
self._max_perf_pct_save = None
self._no_turbo_save = None
self._governors_map = {}
self._cmd = commands()
def _init_devices(self):
self._devices = set()
# current list of devices
for device in self._hardware_inventory.get_devices("cpu"):
self._devices.add(device.sys_name)
self._assigned_devices = set()
self._free_devices = self._devices.copy()
@classmethod
def _get_config_options(self):
return {
"load_threshold" : 0.2,
"latency_low" : 100,
"latency_high" : 1000,
"force_latency" : None,
"governor" : None,
"sampling_down_factor" : None,
"energy_perf_bias" : None,
"min_perf_pct" : None,
"max_perf_pct" : None,
"no_turbo" : None,
}
def _check_energy_perf_bias(self):
self._has_energy_perf_bias = False
retcode = self._cmd.execute(["x86_energy_perf_policy", "-r"], no_errors = [errno.ENOENT])[0]
if retcode == 0:
self._has_energy_perf_bias = True
elif retcode == -1:
log.warning("unable to run x86_energy_perf_policy tool, ignoring CPU energy performance bias, is the tool installed?")
else:
log.warning("your CPU doesn't support MSR_IA32_ENERGY_PERF_BIAS, ignoring CPU energy performance bias")
def _check_intel_pstate(self):
self._has_intel_pstate = os.path.exists("/sys/devices/system/cpu/intel_pstate")
if self._has_intel_pstate:
log.info("intel_pstate detected")
def _is_cpu_online(self, device):
sd = str(device)
return self._cmd.is_cpu_online(str(device).replace("cpu", ""))
def _cpu_has_scaling_governor(self, device):
return os.path.exists("/sys/devices/system/cpu/%s/cpufreq/scaling_governor" % device)
def _check_cpu_can_change_governor(self, device):
if not self._is_cpu_online(device):
log.debug("'%s' is not online, skipping" % device)
return False
if not self._cpu_has_scaling_governor(device):
log.debug("there is no scaling governor fo '%s', skipping" % device)
return False
return True
def _instance_init(self, instance):
instance._has_static_tuning = True
instance._has_dynamic_tuning = False
# only the first instance of the plugin can control the latency
if self._instances.values()[0] == instance:
instance._first_instance = True
try:
self._cpu_latency_fd = os.open(consts.PATH_CPU_DMA_LATENCY, os.O_WRONLY)
except OSError:
log.error("Unable to open '%s', disabling PM_QoS control" % consts.PATH_CPU_DMA_LATENCY)
self._has_pm_qos = False
self._latency = None
if instance.options["force_latency"] is None:
instance._load_monitor = self._monitors_repository.create("load", None)
instance._has_dynamic_tuning = True
else:
instance._load_monitor = None
# Check for x86_energy_perf_policy, ignore if not available / supported
self._check_energy_perf_bias()
# Check for intel_pstate
self._check_intel_pstate()
else:
instance._first_instance = False
log.info("Latency settings from non-first CPU plugin instance '%s' will be ignored." % instance.name)
instance._first_device = list(instance.devices)[0]
def _instance_cleanup(self, instance):
if instance._first_instance:
if self._has_pm_qos:
os.close(self._cpu_latency_fd)
if instance._load_monitor is not None:
self._monitors_repository.delete(instance._load_monitor)
def _get_intel_pstate_attr(self, attr):
return self._cmd.read_file("/sys/devices/system/cpu/intel_pstate/%s" % attr, None).strip()
def _set_intel_pstate_attr(self, attr, val):
if val is not None:
self._cmd.write_to_file("/sys/devices/system/cpu/intel_pstate/%s" % attr, val)
def _getset_intel_pstate_attr(self, attr, value):
if value is None:
return None
v = self._get_intel_pstate_attr(attr)
self._set_intel_pstate_attr(attr, value)
return v
def _instance_apply_static(self, instance):
super(self.__class__, self)._instance_apply_static(instance)
if not instance._first_instance:
return
force_latency_value = instance.options["force_latency"]
if force_latency_value is not None:
self._set_latency(force_latency_value)
if self._has_intel_pstate:
self._min_perf_pct_save = self._getset_intel_pstate_attr("min_perf_pct", instance.options["min_perf_pct"])
self._max_perf_pct_save = self._getset_intel_pstate_attr("max_perf_pct", instance.options["max_perf_pct"])
self._no_turbo_save = self._getset_intel_pstate_attr("no_turbo", instance.options["no_turbo"])
def _instance_unapply_static(self, instance, profile_switch = False):
super(self.__class__, self)._instance_unapply_static(instance, profile_switch)
if instance._first_instance and self._has_intel_pstate:
self._set_intel_pstate_attr("min_perf_pct", self._min_perf_pct_save)
self._set_intel_pstate_attr("max_perf_pct", self._max_perf_pct_save)
self._set_intel_pstate_attr("no_turbo", self._no_turbo_save)
def _instance_apply_dynamic(self, instance, device):
self._instance_update_dynamic(instance, device)
def _instance_update_dynamic(self, instance, device):
assert(instance._first_instance)
if device != instance._first_device:
return
load = instance._load_monitor.get_load()["system"]
if load < instance.options["load_threshold"]:
self._set_latency(instance.options["latency_high"])
else:
self._set_latency(instance.options["latency_low"])
def _instance_unapply_dynamic(self, instance, device):
pass
def _set_latency(self, latency):
latency = int(latency)
if self._has_pm_qos and self._latency != latency:
log.info("setting new cpu latency %d" % latency)
latency_bin = struct.pack("i", latency)
os.write(self._cpu_latency_fd, latency_bin)
self._latency = latency
def _get_available_governors(self, device):
return self._cmd.read_file("/sys/devices/system/cpu/%s/cpufreq/scaling_available_governors" % device).strip().split()
@command_set("governor", per_device=True)
def _set_governor(self, governor, device, sim):
if not self._check_cpu_can_change_governor(device):
return None
if governor not in self._get_available_governors(device):
if not sim:
log.info("ignoring governor '%s' on cpu '%s', it is not supported" % (governor, device))
return None
if not sim:
log.info("setting governor '%s' on cpu '%s'" % (governor, device))
self._cmd.write_to_file("/sys/devices/system/cpu/%s/cpufreq/scaling_governor" % device, str(governor))
return str(governor)
@command_get("governor")
def _get_governor(self, device):
governor = None
if not self._check_cpu_can_change_governor(device):
return None
data = self._cmd.read_file("/sys/devices/system/cpu/%s/cpufreq/scaling_governor" % device).strip()
if len(data) > 0:
governor = data
if governor is None:
log.error("could not get current governor on cpu '%s'" % device)
return governor
def _sampling_down_factor_path(self, governor = "ondemand"):
return "/sys/devices/system/cpu/cpufreq/%s/sampling_down_factor" % governor
@command_set("sampling_down_factor", per_device = True, priority = 10)
def _set_sampling_down_factor(self, sampling_down_factor, device, sim):
val = None
# hack to clear governors map when the profile starts unloading
# TODO: this should be handled better way, by e.g. currently non-implemented
# Plugin.profile_load_finished() method
if device in self._governors_map:
self._governors_map.clear()
self._governors_map[device] = None
governor = self._get_governor(device)
if governor is None:
log.debug("ignoring sampling_down_factor setting for CPU '%s', cannot match governor" % device)
return None
if governor not in self._governors_map.values():
self._governors_map[device] = governor
path = self._sampling_down_factor_path(governor)
if not os.path.exists(path):
log.debug("ignoring sampling_down_factor setting for CPU '%s', governor '%s' doesn't support it" % (device, governor))
return None
val = str(sampling_down_factor)
if not sim:
log.info("setting sampling_down_factor to '%s' for governor '%s'" % (val, governor))
self._cmd.write_to_file(path, val)
return val
@command_get("sampling_down_factor")
def _get_sampling_down_factor(self, device):
governor = self._get_governor(device)
if governor is None:
return None
path = self._sampling_down_factor_path(governor)
if not os.path.exists(path):
return None
return self._cmd.read_file(path).strip()
@command_set("energy_perf_bias", per_device=True)
def _set_energy_perf_bias(self, energy_perf_bias, device, sim):
if not self._is_cpu_online(device):
log.debug("%s is not online, skipping" % device)
return None
if self._has_energy_perf_bias:
if not sim:
cpu_id = device.lstrip("cpu")
log.info("setting energy_perf_bias '%s' on cpu '%s'" % (energy_perf_bias, device))
self._cmd.execute(["x86_energy_perf_policy", "-c", cpu_id, str(energy_perf_bias)])
return str(energy_perf_bias)
else:
return None
def _try_parse_num(self, s):
try:
v = int(s)
except ValueError as e:
try:
v = int(s, 16)
except ValueError as e:
v = s
return v
def _energy_perf_policy_to_human(self, s):
return {0:"performance", 6:"normal", 15:"powersave"}.get(self._try_parse_num(s), s)
@command_get("energy_perf_bias")
def _get_energy_perf_bias(self, device):
energy_perf_bias = None
if not self._is_cpu_online(device):
log.debug("%s is not online, skipping" % device)
return None
if self._has_energy_perf_bias:
cpu_id = device.lstrip("cpu")
retcode, lines = self._cmd.execute(["x86_energy_perf_policy", "-c", cpu_id, "-r"])
if retcode == 0:
for line in lines.splitlines():
l = line.split()
if len(l) == 2:
energy_perf_bias = self._energy_perf_policy_to_human(l[1])
break
return energy_perf_bias