1
0
Fork 0

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:
Jaroslav Škarvada 2014-09-29 16:02:06 +02:00
parent 27059d3faa
commit 5b8a2d2e8e
16 changed files with 213 additions and 16 deletions

29
00_tuned Normal file
View 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\""

View file

@ -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
View 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=

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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()

View 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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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: