plugins: added bootloader plugin
This plugin can be used to add parameters to kernel (boot/dracut) command line. Currently only grub2 bootloader is used and reboot is required to apply the tunings. The tuning can be only unloaded by profile change followed by reboot. It patches grub.cfg at runtime and installs /etc/grub.d/00_tuned grub2 template. So it's possible to regenerate grub.cfg by grub2-mkconfig and the tuning persists. It also patches /etc/default/grub and adds tuned_params grub2 variable to GRUB_CMDLINE_LINUX. This is probably the only way how to get variables into kernel command line in grub.cfg. Nothing else is added to /etc/default and it's patched if the plugin is used for the first time. It's re-patched on plugin initialization only if the tuned_params variable is missing. On subsequent invocation only the tuned_params grub2 variable and grub.cfg are changed. Example of use: [bootloader] cmdline=quiet This adds "quiet" parameter to the kernel boot command line (reboot is required). EFI is also supported. By default it autodetects redhat and fedora directories (in this order). Nothing else is touched. If you have different EFI system and your grub.cfg location is not auto-detected or your grub.cfg is located on different place you can specify the grub.cfg location manually: [bootloader] grub2_cfg_file=CUSTOM_PATH/grub.cfg cmdline=quiet This has precedence over auto-detection. This is used for runtime patching. In case it is not set correctly, the grub.cfg patching will not work, but you can still regenerate the config by hand by grub2-mkconfig. Resolves: rhbz#1044111 Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
parent
27059d3faa
commit
5b8a2d2e8e
16 changed files with 213 additions and 16 deletions
29
00_tuned
Normal file
29
00_tuned
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#! /bin/sh
|
||||
set -e
|
||||
|
||||
# grub-mkconfig helper script.
|
||||
# Copyright (C) 2014 Red Hat, Inc
|
||||
# Author: Jaroslav Škarvada <jskarvad@redhat.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
tunedcfgdir=/etc/tuned
|
||||
tuned_bootcmdline_file=$tunedcfgdir/bootcmdline
|
||||
|
||||
. $tuned_bootcmdline_file
|
||||
|
||||
[ "$TUNED_BOOT_CMDLINE" ] && \
|
||||
echo "set tuned_params=\"$TUNED_BOOT_CMDLINE\""
|
||||
5
Makefile
5
Makefile
|
|
@ -53,6 +53,7 @@ install:
|
|||
install -m 0644 tuned-main.conf $(DESTDIR)/etc/tuned/tuned-main.conf
|
||||
# None profile in the moment, autodetection will be used
|
||||
echo -n > $(DESTDIR)/etc/tuned/active_profile
|
||||
install -m 0644 bootcmdline $(DESTDIR)/etc/tuned/bootcmdline
|
||||
|
||||
# profiles & system config
|
||||
mkdir -p $(DESTDIR)$(TUNED_PROFILESDIR)
|
||||
|
|
@ -79,6 +80,10 @@ install:
|
|||
mkdir -p $(DESTDIR)/etc/dbus-1/system.d
|
||||
install -m 0644 dbus.conf $(DESTDIR)/etc/dbus-1/system.d/com.redhat.tuned.conf
|
||||
|
||||
# grub template
|
||||
mkdir -p $(DESTDIR)/etc/grub.d
|
||||
install -m 0755 00_tuned $(DESTDIR)/etc/grub.d/00_tuned
|
||||
|
||||
# manual pages
|
||||
for man_section in 5 7 8; do \
|
||||
mkdir -p $(DESTDIR)/usr/share/man/man$$man_section; \
|
||||
|
|
|
|||
25
bootcmdline
Normal file
25
bootcmdline
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# This file specifies additional parameters to kernel boot command line.
|
||||
# Its content is set by the tuned bootloader plugin and sourced by the
|
||||
# grub2-mkconfig (/etc/grub.d/00_tuned script).
|
||||
#
|
||||
# Please do not edit this file. Content of this file can be overwritten by
|
||||
# switch of tuned profile.
|
||||
#
|
||||
# If you need to add parameters to the kernel boot command line, create
|
||||
# tuned profile containing the following:
|
||||
#
|
||||
# [bootloader]
|
||||
# cmdline = YOUR_ADDITIONAL_KERNEL_PARAMETERS
|
||||
#
|
||||
# Then switch to your newly created profile by:
|
||||
#
|
||||
# tuned-adm profile YOUR_NEW_PROFILE
|
||||
#
|
||||
# Your current grub2 config will be patched, but you can also
|
||||
# regenerate it anytime by:
|
||||
#
|
||||
# grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||
#
|
||||
# YOUR_ADDITIONAL_KERNEL_PARAMETERS will stay preserved.
|
||||
|
||||
TUNED_BOOT_CMDLINE=
|
||||
|
|
@ -131,7 +131,9 @@ sed -i 's|.*/\([^/]\+\)/[^\.]\+\.conf|\1|' /etc/tuned/active_profile
|
|||
%dir %{_sysconfdir}/tuned
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/active_profile
|
||||
%config(noreplace) %{_sysconfdir}/tuned/tuned-main.conf
|
||||
%config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/tuned/bootcmdline
|
||||
%{_sysconfdir}/dbus-1/system.d/com.redhat.tuned.conf
|
||||
%{_sysconfdir}/grub.d/00_tuned
|
||||
%{_tmpfilesdir}/tuned.conf
|
||||
%{_unitdir}/tuned.service
|
||||
%dir %{_localstatedir}/log/tuned
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ DBUS_OBJECT = "/Tuned"
|
|||
DEFAULT_PROFILE = "balanced"
|
||||
DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle"
|
||||
LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"]
|
||||
GRUB2_CFG_FILES = ["/boot/grub2/grub.cfg", "/boot/efi/EFI/redhat/grub.cfg", "/boot/efi/EFI/fedora/grub.cfg"]
|
||||
GRUB2_TUNED_TEMPLATE_NAME = "00_tuned"
|
||||
GRUB2_TEMPLATE_HEADER_BEGIN = "### BEGIN /etc/grub.d/" + GRUB2_TUNED_TEMPLATE_NAME + " ###"
|
||||
GRUB2_TEMPLATE_HEADER_END = "### END /etc/grub.d/" + GRUB2_TUNED_TEMPLATE_NAME + " ###"
|
||||
GRUB2_TUNED_VAR = "tuned_params"
|
||||
GRUB2_DEFAULT_ENV_FILE = "/etc/default/grub"
|
||||
BOOT_CMDLINE_TUNED_VAR = "TUNED_BOOT_CMDLINE"
|
||||
BOOT_CMDLINE_FILE = "/etc/tuned/bootcmdline"
|
||||
|
||||
# number of backups
|
||||
LOG_FILE_COUNT = 2
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
success = True
|
||||
try:
|
||||
if was_running:
|
||||
self._daemon.stop()
|
||||
# stop(switch_profile = True), due to profile switch
|
||||
self._daemon.stop(True)
|
||||
self._daemon.set_profile(profile_name)
|
||||
except tuned.exceptions.TunedException as e:
|
||||
success = False
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class Daemon(object):
|
|||
def _init_threads(self):
|
||||
self._thread = None
|
||||
self._terminate = threading.Event()
|
||||
# Flag which is set if terminating due to profile_switch
|
||||
self._terminate_profile_switch = threading.Event()
|
||||
|
||||
def _init_profile(self, profile_name):
|
||||
if profile_name is None:
|
||||
|
|
@ -102,7 +104,12 @@ class Daemon(object):
|
|||
log.debug("performing tunings")
|
||||
self._unit_manager.update_tuning()
|
||||
|
||||
self._unit_manager.stop_tuning()
|
||||
# if terminating due to profile switch
|
||||
if self._terminate_profile_switch.is_set():
|
||||
profile_switch = True
|
||||
else:
|
||||
profile_switch = False
|
||||
self._unit_manager.stop_tuning(profile_switch)
|
||||
self._unit_manager.destroy_all()
|
||||
|
||||
def _save_active_profile(self, profile_name):
|
||||
|
|
@ -153,14 +160,18 @@ class Daemon(object):
|
|||
|
||||
log.info("starting tuning")
|
||||
self._thread = threading.Thread(target=self._thread_code)
|
||||
self._terminate_profile_switch.clear()
|
||||
self._terminate.clear()
|
||||
self._thread.start()
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
# 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():
|
||||
return False
|
||||
log.info("stopping tunning")
|
||||
if profile_switch:
|
||||
self._terminate_profile_switch.set()
|
||||
self._terminate.set()
|
||||
self._thread.join()
|
||||
self._thread = None
|
||||
|
|
|
|||
|
|
@ -193,20 +193,21 @@ 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_update_dynamic)
|
||||
|
||||
def instance_unapply_tuning(self, instance):
|
||||
# profile_switch is true if unapplying tuning due to profile switch
|
||||
def instance_unapply_tuning(self, instance, profile_switch = False):
|
||||
"""
|
||||
Remove all tunings applied by the plugin instance.
|
||||
"""
|
||||
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_unapply_dynamic)
|
||||
if instance.has_static_tuning:
|
||||
self._instance_unapply_static(instance)
|
||||
self._instance_unapply_static(instance, profile_switch)
|
||||
|
||||
def _instance_apply_static(self, instance):
|
||||
self._execute_all_non_device_commands(instance)
|
||||
self._execute_all_device_commands(instance, instance.devices)
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
def _instance_unapply_static(self, instance, profile_switch = False):
|
||||
self._cleanup_all_device_commands(instance, instance.devices)
|
||||
self._cleanup_all_non_device_commands(instance)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ class Instance(object):
|
|||
def update_tuning(self):
|
||||
self._plugin.instance_update_tuning(self)
|
||||
|
||||
def unapply_tuning(self):
|
||||
self._plugin.instance_unapply_tuning(self)
|
||||
def unapply_tuning(self, profile_switch = False):
|
||||
self._plugin.instance_unapply_tuning(self, profile_switch)
|
||||
|
||||
def destroy(self):
|
||||
self.unapply_tuning()
|
||||
|
|
|
|||
108
tuned/plugins/plugin_bootloader.py
Normal file
108
tuned/plugins/plugin_bootloader.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import base
|
||||
from decorators import *
|
||||
import tuned.logs
|
||||
from tuned.utils.commands import commands
|
||||
import tuned.consts as consts
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
class BootloaderPlugin(base.Plugin):
|
||||
"""
|
||||
Plugin for tuning bootloader options.
|
||||
|
||||
Currently only grub2 is supported and reboot is required to apply the tunings.
|
||||
These tunings are unloaded only on profile change followed by reboot.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
self._cmd = commands()
|
||||
|
||||
def _instance_init(self, instance):
|
||||
instance._has_dynamic_tuning = False
|
||||
instance._has_static_tuning = True
|
||||
self._grub2_cfg_file = self._get_grub2_cfg_file()
|
||||
|
||||
def _instance_cleanup(self, instance):
|
||||
pass
|
||||
|
||||
def _get_config_options(cls):
|
||||
return {
|
||||
"grub2_cfg_file": None,
|
||||
"cmdline": "",
|
||||
}
|
||||
|
||||
def _get_grub2_cfg_file(self):
|
||||
for f in consts.GRUB2_CFG_FILES:
|
||||
if os.path.exists(f):
|
||||
return f
|
||||
return None
|
||||
|
||||
def _patch_bootcmdline(self, value):
|
||||
return self._cmd.replace_in_file(consts.BOOT_CMDLINE_FILE, r"\b(" + consts.BOOT_CMDLINE_TUNED_VAR + r"\s*=).*$", r"\1" + str(value))
|
||||
|
||||
def _remove_grub2_tuning(self):
|
||||
self._patch_bootcmdline("")
|
||||
self._cmd.replace_in_file(self._grub2_cfg_file, r"\b(set " + consts.GRUB2_TUNED_VAR + r"\s*=).*$", r"\1")
|
||||
|
||||
def _instance_unapply_static(self, instance, profile_switch = False):
|
||||
if profile_switch:
|
||||
log.info("removing grub2 command line previously added by tuned")
|
||||
self._remove_grub2_tuning()
|
||||
|
||||
def _grub2_cfg_unpatch(self, grub2_cfg):
|
||||
log.debug("unpatching grub.cfg")
|
||||
cfg = re.sub(r"\bset " + consts.GRUB2_TUNED_VAR + "\s*=.*$", "", grub2_cfg, flags = re.MULTILINE)
|
||||
grub2_cfg = re.sub(r"\$" + consts.GRUB2_TUNED_VAR, "", cfg, flags = re.MULTILINE)
|
||||
cfg = re.sub(consts.GRUB2_TEMPLATE_HEADER_BEGIN, "", grub2_cfg, flags = re.MULTILINE)
|
||||
return re.sub(consts.GRUB2_TEMPLATE_HEADER_END, "", cfg, flags = re.MULTILINE)
|
||||
|
||||
def _grub2_cfg_patch_initial(self, grub2_cfg, value):
|
||||
log.debug("initial patching of grub.cfg")
|
||||
cfg = re.sub(r"^(\s*###\s+END\s+[^#]+/00_header\s+###\s*)$", r"\1" + "\n" + consts.GRUB2_TEMPLATE_HEADER_BEGIN + "\nset " +
|
||||
consts.GRUB2_TUNED_VAR + "=\"" + str(value) + "\"\n" + consts.GRUB2_TEMPLATE_HEADER_END + "\n", grub2_cfg, flags = re.MULTILINE)
|
||||
return re.sub(r"^(\s*linux(16|efi)\s+.*)$", r"\1 $" + consts.GRUB2_TUNED_VAR, cfg, flags = re.MULTILINE)
|
||||
|
||||
def _grub2_default_env_patch(self):
|
||||
grub2_default_env = self._cmd.read_file(consts.GRUB2_DEFAULT_ENV_FILE)
|
||||
if len(grub2_default_env) <= 0:
|
||||
log.error("error reading '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
|
||||
return False
|
||||
|
||||
if re.search(r"^[^#]*\bGRUB_CMDLINE_LINUX\s*=.*\\\$" + consts.GRUB2_TUNED_VAR + r"\b.*$", grub2_default_env, flags = re.MULTILINE) is None:
|
||||
log.debug("patching '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
|
||||
self._cmd.write_to_file(consts.GRUB2_DEFAULT_ENV_FILE,
|
||||
grub2_default_env + "GRUB_CMDLINE_LINUX=\"$GRUB_CMDLINE_LINUX " + r"\$" + consts.GRUB2_TUNED_VAR + "\"\n")
|
||||
return True
|
||||
|
||||
def _grub2_cfg_patch(self, value):
|
||||
log.debug("patching grub.cfg")
|
||||
if self._grub2_cfg_file is None:
|
||||
log.error("cannot find grub.cfg to patch, you need to regenerate it by hand by grub2-mkconfig")
|
||||
return False
|
||||
grub2_cfg = self._cmd.read_file(self._grub2_cfg_file)
|
||||
if len(grub2_cfg) <= 0:
|
||||
log.error("error patching %s, you need to regenerate it by hand by grub2-mkconfig" % self._grub2_cfg_file)
|
||||
return False
|
||||
log.debug("adding boot command line parameters to '%s'" % self._grub2_cfg_file)
|
||||
(grub2_cfg_new, nsubs) = re.subn(r"\b(set " + consts.GRUB2_TUNED_VAR + "\s*=).*$", r"\1" + "\"" + str(value) + "\"", grub2_cfg, flags = re.MULTILINE)
|
||||
if nsubs < 1:
|
||||
grub2_cfg_new = self._grub2_cfg_patch_initial(self._grub2_cfg_unpatch(grub2_cfg), value)
|
||||
self._cmd.write_to_file(self._grub2_cfg_file, grub2_cfg_new)
|
||||
self._grub2_default_env_patch()
|
||||
return True
|
||||
|
||||
@command_custom("grub2_cfg_file")
|
||||
def _grub2_cfg_file(self, enabling, value):
|
||||
if enabling and value is not None:
|
||||
self._grub2_cfg_file = value
|
||||
|
||||
@command_custom("cmdline", per_device = False, priority = 10)
|
||||
def _cmdline(self, enabling, value):
|
||||
if enabling:
|
||||
log.info("installing additional boot command line parameters to grub2")
|
||||
self._grub2_cfg_patch(value)
|
||||
self._patch_bootcmdline(value)
|
||||
|
|
@ -125,8 +125,8 @@ class CPULatencyPlugin(base.Plugin):
|
|||
self._set_intel_pstate_attr("max_perf_pct", instance.options["max_perf_pct"])
|
||||
self._set_intel_pstate_attr("no_turbo", instance.options["no_turbo"])
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
super(self.__class__, self)._instance_unapply_static(instance)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,6 @@ class ScriptPlugin(base.Plugin):
|
|||
super(self.__class__, self)._instance_apply_static(instance)
|
||||
self._call_scripts(instance._scripts, "start")
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
def _instance_unapply_static(self, instance, profile_switch = False):
|
||||
self._call_scripts(reversed(instance._scripts), "stop")
|
||||
super(self.__class__, self)._instance_unapply_static(instance)
|
||||
super(self.__class__, self)._instance_unapply_static(instance, profile_switch)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class SysctlPlugin(base.Plugin):
|
|||
|
||||
self._storage.set("options", instance._sysctl_original)
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
def _instance_unapply_static(self, instance, profile_switch = False):
|
||||
for option, value in instance._sysctl_original.iteritems():
|
||||
self._write_sysctl(option, value)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class SysfsPlugin(base.Plugin):
|
|||
else:
|
||||
log.error("rejecting write to '%s' (not inside /sys)" % key)
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
def _instance_unapply_static(self, instance, profile_switch = False):
|
||||
for key, value in instance._sysfs_original.iteritems():
|
||||
self._write_sysfs(key, value)
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ class Manager(object):
|
|||
for instance in self._instances:
|
||||
instance.update_tuning()
|
||||
|
||||
def stop_tuning(self):
|
||||
# profile_switch is helper telling plugins whether the stop is due to profile switch
|
||||
def stop_tuning(self, profile_switch = False):
|
||||
for instance in self._instances:
|
||||
instance.unapply_tuning()
|
||||
instance.unapply_tuning(profile_switch)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ class commands:
|
|||
self._error("Reading %s error: %s" % (f, e))
|
||||
return old_value
|
||||
|
||||
def replace_in_file(self, f, pattern, repl):
|
||||
data = self.read_file(f)
|
||||
if len(data) <= 0:
|
||||
return False;
|
||||
return self.write_to_file(f, re.sub(pattern, repl, data, flags = re.MULTILINE))
|
||||
|
||||
def execute(self, args):
|
||||
retcode = None
|
||||
if self._environment is None:
|
||||
|
|
|
|||
Loading…
Reference in a new issue