1
0
Fork 0

tuned: added verify command

The verify command verifies whether the current system settings matches
activated profile. It also writes all settings from the active profile
with the current / expected values to the log. It is good for checking
what is exactly set and what changed during the run.

Currently custom commands and plugin_scheduler are not supported.

Resolves: rhbz#1150047
Fixes: #34

Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
Jaroslav Škarvada 2015-05-18 18:55:01 +02:00
parent da99153f99
commit 6940d280b6
19 changed files with 317 additions and 77 deletions

View file

@ -48,6 +48,9 @@ if __name__ == "__main__":
parser_off = subparsers.add_parser("recommend", help="recommend profile")
parser_off.set_defaults(action="recommend_profile")
parser_off = subparsers.add_parser("verify", help="verify profile")
parser_off.set_defaults(action="verify_profile")
args = parser.parse_args(sys.argv[1:])
options = vars(args)

View file

@ -87,6 +87,23 @@ class Admin(object):
profile = self._cmd.recommend_profile()
print profile
def verify_profile(self):
ret = False
try:
ret = self._controller.verify_profile()
except TunedAdminDBusException as e:
self._error(e)
self._error("Cannot verify profile if there is no connection to daemon")
return False
if ret:
print "Verfication succeeded, current system settings match the preset profile."
else:
print "Verification failed, current system settings differ from the preset profile."
print "See tuned.log for details. You can mostly fix this by Tuned restart:"
print " systemctl restart tuned"
return ret
def off(self):
result = self._controller.off()
if not result:

View file

@ -55,5 +55,8 @@ class DBusController(object):
def recommend_profile(self):
return self._call("recommend_profile")
def verify_profile(self):
return self._call("verify_profile")
def off(self):
return self._call("disable")

View file

@ -106,3 +106,7 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
@exports.export("", "s")
def recommend_profile(self):
return self._cmd.recommend_profile()
@exports.export("", "b")
def verify_profile(self):
return self._daemon.verify_profile()

View file

@ -45,6 +45,10 @@ class Daemon(object):
self._terminate = threading.Event()
# Flag which is set if terminating due to profile_switch
self._terminate_profile_switch = threading.Event()
# Flag which is set if there is no operation in progress
self._not_used = threading.Event()
self._not_used.set()
self._profile_applied = threading.Event()
def _init_profile(self, profile_name):
if profile_name is None:
@ -87,6 +91,7 @@ class Daemon(object):
self._unit_manager.create(self._profile.units)
self._save_active_profile(self._profile.name)
self._unit_manager.start_tuning()
self._profile_applied.set()
# In python 2 interpreter with applied patch for rhbz#917709 we need to periodically
# poll, otherwise the python will not have chance to update events / locks (due to GIL)
@ -104,6 +109,14 @@ class Daemon(object):
log.debug("performing tunings")
self._unit_manager.update_tuning()
self._profile_applied.clear()
# wait for others to complete their tasks, use timeout 3 x sleep_interval to prevent
# deadlocks
i = 0
while not self._cmd.wait(self._not_used, self._sleep_interval) and i < 3:
i += 1
# if terminating due to profile switch
if self._terminate_profile_switch.is_set():
profile_switch = True
@ -159,12 +172,34 @@ class Daemon(object):
return False
log.info("starting tuning")
self._not_used.set()
self._thread = threading.Thread(target=self._thread_code)
self._terminate_profile_switch.clear()
self._terminate.clear()
self._thread.start()
return True
def verify_profile(self):
if not self.is_running():
log.error("tuned is not running")
return False
if self._profile is None:
log.error("no profile is set")
return False
if not self._profile_applied.is_set():
log.error("profile is not applied")
return False
# using deamon, the main loop mustn't exit before our completion
self._not_used.clear()
log.info("verifying profile(s): %s" % self._profile.name)
ret = self._unit_manager.verify_tuning()
# main loop is allowed to exit
self._not_used.set()
return ret
# profile_switch is helper telling plugins whether the stop is due to profile switch
def stop(self, profile_switch = False):
if not self.is_running():

View file

@ -184,6 +184,18 @@ class Plugin(object):
if instance.has_dynamic_tuning and self._global_cfg.get("dynamic_tuning", consts.CFG_DEF_DYNAMIC_TUNING):
self._run_for_each_device(instance, self._instance_apply_dynamic)
def instance_verify_tuning(self, instance):
"""
Verify static tuning if the plugin instance is active.
"""
if not instance.active:
return None
if instance.has_static_tuning:
return self._instance_verify_static(instance)
else:
return None
def instance_update_tuning(self, instance):
"""
Apply dynamic tuning if the plugin instance is active.
@ -207,6 +219,14 @@ class Plugin(object):
self._execute_all_non_device_commands(instance)
self._execute_all_device_commands(instance, instance.devices)
def _instance_verify_static(self, instance):
ret = True
if self._verify_all_non_device_commands(instance) == False:
ret = False
if self._verify_all_device_commands(instance, instance.devices) == False:
ret = False
return ret
def _instance_unapply_static(self, instance, profile_switch = False):
self._cleanup_all_device_commands(instance, instance.devices)
self._cleanup_all_non_device_commands(instance)
@ -301,7 +321,7 @@ class Plugin(object):
return self._storage.unset(key)
#
# Command execution and cleanup.
# Command execution, verification, and cleanup.
#
def _execute_all_non_device_commands(self, instance):
@ -318,32 +338,58 @@ class Plugin(object):
for device in devices:
self._execute_device_command(instance, command, device, new_value)
def _check_and_save_value(self, instance, command, device = None, new_value = None):
if device is not None:
current_value = command["get"](device)
else:
current_value = command["get"]()
def _verify_all_non_device_commands(self, instance):
ret = True
for command in filter(lambda command: not command["per_device"], self._commands.values()):
new_value = instance.options.get(command["name"], None)
if new_value is not None:
if self._verify_non_device_command(instance, command, new_value) == False:
ret = False
return ret
def _verify_all_device_commands(self, instance, devices):
ret = True
for command in filter(lambda command: command["per_device"], self._commands.values()):
new_value = instance.options.get(command["name"], None)
if new_value is None:
continue
for device in devices:
if self._verify_device_command(instance, command, device, new_value) == False:
ret = False
return ret
def _process_assignment_modifiers(self, new_value, current_value):
if new_value is not None:
nws = str(new_value)
op = nws[:1]
val = nws[1:]
if current_value is None:
return val
try:
if op == ">":
if int(val) > int(current_value):
new_value = val;
return val
else:
current_value = None
new_value = None
return None
elif op == "<":
if int(val) < int(current_value):
new_value = val;
return val
else:
current_value = None
new_value = None
return None
except ValueError:
log.warn("cannot compare new value '%s' with current value '%s' by operator '%s', using '%s' directly as new value" % (val, current_value, op, new_value))
return new_value
if current_value is not None:
def _get_current_value(self, command, device = None):
if device is not None:
return command["get"](device)
else:
return command["get"]()
def _check_and_save_value(self, instance, command, device = None, new_value = None):
current_value = self._get_current_value(command, device)
new_value = self._process_assignment_modifiers(new_value, current_value)
if new_value is not None and current_value is not None:
self._storage_set(instance, command, current_value, device)
return new_value
@ -353,7 +399,7 @@ class Plugin(object):
else:
new_value = self._check_and_save_value(instance, command, device, new_value)
if new_value is not None:
command["set"](new_value, device)
command["set"](new_value, device, sim = False)
def _execute_non_device_command(self, instance, command, new_value):
if command["custom"] is not None:
@ -361,7 +407,43 @@ class Plugin(object):
else:
new_value = self._check_and_save_value(instance, command, None, new_value)
if new_value is not None:
command["set"](new_value)
command["set"](new_value, sim = False)
def _verify_device_command(self, instance, command, device, new_value):
# custom commands not supported for verification
if command["custom"] is not None:
return None
current_value = self._get_current_value(command, device)
new_value = self._process_assignment_modifiers(new_value, current_value)
if new_value is None:
return None
new_value = command["set"](new_value, device, sim = True)
if new_value is None:
return None
if new_value == current_value:
log.info("verify: device %s: %s = %s" % (device, command["name"], current_value))
return True
else:
log.info("verify: device %s: %s = %s, expected %s" % (device, command["name"], current_value, new_value))
return False
def _verify_non_device_command(self, instance, command, new_value):
# custom commands not supported for verification
if command["custom"] is not None:
return None
current_value = self._get_current_value(command)
new_value = self._process_assignment_modifiers(new_value, current_value)
if new_value is None:
return None
new_value = command["set"](new_value, sim = True)
if new_value is None:
return None
if new_value == current_value:
log.info("verify: %s = %s" % (command["name"], current_value))
return True
else:
log.info("verify: %s = %s, expected %s" % (command["name"], current_value, new_value))
return False
def _cleanup_all_non_device_commands(self, instance):
for command in filter(lambda command: not command["per_device"], self._commands.values()):
@ -380,7 +462,7 @@ class Plugin(object):
else:
old_value = self._storage_get(instance, command, device)
if old_value is not None:
command["set"](old_value, device)
command["set"](old_value, device, sim = False)
self._storage_unset(instance, command, device)
def _cleanup_non_device_command(self, instance, command):
@ -389,5 +471,5 @@ class Plugin(object):
else:
old_value = self._storage_get(instance, command)
if old_value is not None:
command["set"](old_value)
command["set"](old_value, sim = False)
self._storage_unset(instance, command)

View file

@ -53,6 +53,9 @@ class Instance(object):
def apply_tuning(self):
self._plugin.instance_apply_tuning(self)
def verify_tuning(self):
return self._plugin.instance_verify_tuning(self)
def update_tuning(self):
self._plugin.instance_update_tuning(self)

View file

@ -55,11 +55,15 @@ class AudioPlugin(base.Plugin):
return "/sys/module/%s/parameters/power_save_controller" % device
@command_set("timeout", per_device=True)
def _set_timeout(self, value, device):
def _set_timeout(self, value, device, sim):
timeout = int(value)
if timeout >= 0:
sys_file = self._timeout_path(device)
cmd.write_to_file(sys_file, "%d" % timeout)
if not sim:
cmd.write_to_file(sys_file, "%d" % timeout)
return timeout
else:
return None
@command_get("timeout")
def _get_timeout(self, device):

View file

@ -151,7 +151,7 @@ class CPULatencyPlugin(base.Plugin):
pass
def _get_intel_pstate_attr(self, attr):
return self._cmd.read_file("/sys/devices/system/cpu/intel_pstate/%s" % attr, None)
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:
@ -166,19 +166,22 @@ class CPULatencyPlugin(base.Plugin):
self._latency = latency
def _get_available_governors(self, device):
return self._cmd.read_file("/sys/devices/system/cpu/%s/cpufreq/scaling_available_governors" % device).split()
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):
def _set_governor(self, governor, device, sim):
if governor not in self._get_available_governors(device):
log.info("ignoring governor '%s' on cpu '%s', it is not supported" % (governor, device))
return
log.info("setting governor '%s' on cpu '%s'" % (governor, device))
if self._has_cpupower:
cpu_id = device.lstrip("cpu")
self._cmd.execute(["cpupower", "-c", cpu_id, "frequency-set", "-g", str(governor)])
else:
self._cmd.write_to_file("/sys/devices/system/cpu/%s/cpufreq/scaling_governor" % device, str(governor))
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))
if self._has_cpupower:
cpu_id = device.lstrip("cpu")
self._cmd.execute(["cpupower", "-c", cpu_id, "frequency-set", "-g", str(governor)])
else:
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):
@ -206,11 +209,15 @@ class CPULatencyPlugin(base.Plugin):
return governor
@command_set("energy_perf_bias", per_device=True)
def _set_energy_perf_bias(self, energy_perf_bias, device):
def _set_energy_perf_bias(self, energy_perf_bias, device, sim):
if self._has_energy_perf_bias:
log.info("setting energy_perf_bias '%s' on cpu '%s'" % (energy_perf_bias, device))
cpu_id = device.lstrip("cpu")
self._cmd.execute(["x86_energy_perf_policy", "-c", cpu_id, str(energy_perf_bias)])
if not sim:
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:

View file

@ -196,9 +196,11 @@ class DiskPlugin(hotplug.Plugin):
return os.path.join("/sys/block/", device, "queue/scheduler")
@command_set("elevator", per_device=True)
def _set_elevator(self, value, device):
def _set_elevator(self, value, device, sim):
sys_file = self._elevator_file(device)
self._cmd.write_to_file(sys_file, value)
if not sim:
self._cmd.write_to_file(sys_file, value)
return value
@command_get("elevator")
def _get_elevator(self, device):
@ -228,21 +230,27 @@ class DiskPlugin(hotplug.Plugin):
return policy_files
@command_set("alpm")
def _set_alpm(self, policy):
for policy_file in self._alpm_policy_files():
self._cmd.write_to_file(policy_file, policy)
def _set_alpm(self, policy, sim):
if not sim:
for policy_file in self._alpm_policy_files():
self._cmd.write_to_file(policy_file, policy)
return policy
@command_get("alpm")
def _get_alpm(self):
for policy_file in self._alpm_policy_files():
return self._cmd.read_file(policy_file)
return self._cmd.read_file(policy_file).strip()
return None
@command_set("apm", per_device=True)
def _set_apm(self, value, device):
def _set_apm(self, value, device, sim):
if self._apm_errcnt < consts.ERROR_THRESHOLD:
(rc, out) = self._cmd.execute(["hdparm", "-B", str(value), "/dev/" + device])
self._update_apm_errcnt(rc)
if not sim:
(rc, out) = self._cmd.execute(["hdparm", "-B", str(value), "/dev/" + device])
self._update_apm_errcnt(rc)
return str(value)
else:
return None
@command_get("apm")
def _get_apm(self, device):
@ -256,10 +264,14 @@ class DiskPlugin(hotplug.Plugin):
return value
@command_set("spindown", per_device=True)
def _set_spindown(self, value, device):
def _set_spindown(self, value, device, sim):
if self._spindown_errcnt < consts.ERROR_THRESHOLD:
(rc, out) = self._cmd.execute(["hdparm", "-S", str(value), "/dev/" + device])
self._update_spindown_errcnt(rc)
if not sim:
(rc, out) = self._cmd.execute(["hdparm", "-S", str(value), "/dev/" + device])
self._update_spindown_errcnt(rc)
return str(value)
else:
return None
@command_get("spindown")
def _get_spindown(self, device):
@ -278,9 +290,12 @@ class DiskPlugin(hotplug.Plugin):
return v
@command_set("readahead", per_device=True)
def _set_readahead(self, value, device):
def _set_readahead(self, value, device, sim):
sys_file = self._readahead_file(device)
self._cmd.write_to_file(sys_file, "%d" % self._parse_ra(value))
val = self._parse_ra(value)
if not sim:
self._cmd.write_to_file(sys_file, "%d" % val)
return val
@command_get("readahead")
def _get_readahead(self, device):
@ -311,9 +326,11 @@ class DiskPlugin(hotplug.Plugin):
return os.path.join("/sys/block/", device, "queue/iosched/quantum")
@command_set("scheduler_quantum", per_device=True)
def _set_scheduler_quantum(self, value, device):
def _set_scheduler_quantum(self, value, device, sim):
sys_file = self._scheduler_quantum_file(device)
self._cmd.write_to_file(sys_file, "%d" % int(value))
if not sim:
self._cmd.write_to_file(sys_file, "%d" % int(value))
return value
@command_get("scheduler_quantum")
def _get_scheduler_quantum(self, device):

View file

@ -129,17 +129,19 @@ class NetTuningPlugin(base.Plugin):
return "/sys/module/nf_conntrack/parameters/hashsize"
@command_set("wake_on_lan", per_device=True)
def _set_wake_on_lan(self, value, device):
def _set_wake_on_lan(self, value, device, sim):
if value is None:
return
return None
# see man ethtool for possible wol values, 0 added as an alias for 'd'
value = re.sub(r"0", "d", str(value));
if not re.match(r"^[" + WOL_VALUES + r"]+$", value):
log.warn("Incorrect 'wake_on_lan' value.")
return
return None
self._cmd.execute(["ethtool", "-s", device, "wol", value])
if not sim:
self._cmd.execute(["ethtool", "-s", device, "wol", value])
return value
@command_get("wake_on_lan")
def _get_wake_on_lan(self, device):
@ -153,13 +155,17 @@ class NetTuningPlugin(base.Plugin):
return value
@command_set("nf_conntrack_hashsize")
def _set_nf_conntrack_hashsize(self, value):
def _set_nf_conntrack_hashsize(self, value, sim):
if value is None:
return
return None
hashsize = int(value)
if hashsize >= 0:
self._cmd.write_to_file(self._nf_conntrack_hashsize_path(), hashsize)
if not sim:
self._cmd.write_to_file(self._nf_conntrack_hashsize_path(), hashsize)
return hashsize
else:
return None
@command_get("nf_conntrack_hashsize")
def _get_nf_conntrack_hashsize(self):

View file

@ -41,12 +41,16 @@ class SelinuxPlugin(base.Plugin):
pass
@command_set("avc_cache_threshold")
def _set_avc_cache_threshold(self, value):
def _set_avc_cache_threshold(self, value, sim):
if value is None:
return
return None
threshold = int(value)
if threshold >= 0:
self._cmd.write_to_file(self._cache_threshold_path, threshold)
if not sim:
self._cmd.write_to_file(self._cache_threshold_path, threshold)
return threshold
else:
return None
@command_get("avc_cache_threshold")
def _get_avc_cache_threshold(self):

View file

@ -46,6 +46,17 @@ class SysctlPlugin(base.Plugin):
self._storage.set("options", instance._sysctl_original)
def _instance_verify_static(self, instance):
ret = True
for option, value in instance._sysctl.iteritems():
curr_val = self._read_sysctl(option)
if curr_val == value:
log.info("verify: %s = %s" % (option, curr_val))
else:
ret = False
log.info("verify: %s = %s, expected %s" % (option, curr_val, value))
return ret
def _instance_unapply_static(self, instance, profile_switch = False):
for option, value in instance._sysctl_original.iteritems():
self._write_sysctl(option, value)

View file

@ -38,6 +38,18 @@ class SysfsPlugin(base.Plugin):
else:
log.error("rejecting write to '%s' (not inside /sys)" % key)
def _instance_verify_static(self, instance):
ret = True
for key, value in instance._sysfs.iteritems():
if self._check_sysfs(key):
curr_val = self._read_sysfs(key)
if curr_val == value:
log.info("verify: %s = %s" % (key, curr_val))
else:
ret = False
log.info("verify: %s = %s, expectede %s" % (key, curr_val, value))
return ret
def _instance_unapply_static(self, instance, profile_switch = False):
for key, value in instance._sysfs_original.iteritems():
self._write_sysfs(key, value)

View file

@ -37,15 +37,18 @@ class USBPlugin(base.Plugin):
return "/sys/bus/usb/devices/%s/power/autosuspend" % device
@command_set("autosuspend", per_device=True)
def _set_autosuspend(self, value, device):
def _set_autosuspend(self, value, device, sim):
enable = self._option_bool(value)
if enable is None:
return
return None
sys_file = self._autosuspend_sysfile(device)
self._cmd.write_to_file(sys_file, "1" if enable else "0")
val = "1" if enable else "0"
if not sim:
sys_file = self._autosuspend_sysfile(device)
self._cmd.write_to_file(sys_file, val)
return val
@command_get("autosuspend")
def _get_autosuspend(self, device):
sys_file = self._autosuspend_sysfile(device)
return self._cmd.read_file(sys_file)
return self._cmd.read_file(sys_file).strip()

View file

@ -41,21 +41,35 @@ class VideoPlugin(base.Plugin):
}
@command_set("radeon_powersave", per_device=True)
def _set_radeon_powersave(self, value, device):
def _set_radeon_powersave(self, value, device, sim):
sys_files = self._radeon_powersave_files(device)
if not os.path.exists(sys_files["method"]):
log.warn("radeon_powersave is not supported on '%s'" % device)
return
if not sim:
log.warn("radeon_powersave is not supported on '%s'" % device)
return None
if value in ["default", "auto", "low", "mid", "high"]:
self._cmd.write_to_file(sys_files["method"], "profile")
self._cmd.write_to_file(sys_files["profile"], value)
if not sim:
self._cmd.write_to_file(sys_files["method"], "profile")
self._cmd.write_to_file(sys_files["profile"], value)
return value
elif value == "dynpm":
self._cmd.write_to_file(sys_files["method"], "dynpm")
if not sim:
self._cmd.write_to_file(sys_files["method"], "dynpm")
return "dynpm"
else:
log.warn("Invalid option for radeon_powersave.")
if not sim:
log.warn("Invalid option for radeon_powersave.")
return None
@command_get("radeon_powersave")
def _get_radeon_powersave(self, device):
sys_files = self._radeon_powersave_files(device)
return self._cmd.read_file(sys_files["profile"])
method = self._cmd.read_file(sys_files["method"]).strip()
if method == "profile":
return self._cmd.read_file(sys_files["profile"]).strip()
elif method == "dynpm":
return "dynpm"
else:
return None

View file

@ -35,16 +35,21 @@ class VMPlugin(base.Plugin):
return path
@command_set("transparent_hugepages")
def _set_transparent_hugepages(self, value):
def _set_transparent_hugepages(self, value, sim):
if value not in ["always", "never"]:
log.warn("Incorrect 'transparent_hugepages' value.")
return
if not sim:
log.warn("Incorrect 'transparent_hugepages' value.")
return None
sys_file = self._thp_file()
if os.path.exists(sys_file):
cmd.write_to_file(sys_file, value)
if not sim:
cmd.write_to_file(sys_file, value)
return value
else:
log.warn("Option 'transparent_hugepages' is not supported on current hardware.")
if not sim:
log.warn("Option 'transparent_hugepages' is not supported on current hardware.")
return None
@command_get("transparent_hugepages")
def _get_transparent_hugepages(self):

View file

@ -80,6 +80,13 @@ class Manager(object):
for instance in self._instances:
instance.apply_tuning()
def verify_tuning(self):
ret = True
for instance in self._instances:
if instance.verify_tuning() == False:
ret = False
return ret
def update_tuning(self):
for instance in self._instances:
instance.update_tuning()

View file

@ -104,6 +104,9 @@ class commands:
profile = section
return profile
# Do not make balancing on patched Python 2 interpreter (rhbz#1028122).
# It means less CPU usage on patchet interpreter. On non-patched interpreter
# it is not allowed to sleep longer than 50 ms.
def wait(self, terminate, time):
try:
return terminate.wait(time, False)