Hi, Anatoly, Thanks for these patches, good work. Regarding "update-grub": IIRC, this is Ubuntu specific command (and also used in Debian/Debian based flavors). In Fedora (RedHat based) recent distros, you use grub2-mkconfig instead (and there is no "update-grub", IIRC). If this is true, I would consider adding a comment in the commit log saying something like "it is for Ubuntu/Debian-based distros". So maybe in the future someone will add the python code which detects the OS (using lsb-release, etc) and, in case it is Fedora/RedHat distro, the grub2-mkconfig util will be invoked instead. Regards, KW
On Mon, Jun 25, 2018 at 6:59 PM, Anatoly Burakov <anatoly.bura...@intel.com> wrote: > This library is highly experimental and can kill kittens, but its > main purpose is to automatically set up GRUB command-line to > allocate a given number of hugepages at boot time. It works in > a similar way HugeUtil library does, but instead of committing > changes to fstab or runtime configuration, it commits its > changes to GRUB default command-line and updates all GRUB entries > afterwards. I got it to a state where it's safe to use on my > system, but see the part above about killing kittens - you have > been warned :) > > No example scripts will currently be provided. > > Signed-off-by: Anatoly Burakov <anatoly.bura...@intel.com> > --- > usertools/DPDKConfigLib/GrubHugeUtil.py | 175 ++++++++++++++++++++++++ > 1 file changed, 175 insertions(+) > create mode 100755 usertools/DPDKConfigLib/GrubHugeUtil.py > > diff --git a/usertools/DPDKConfigLib/GrubHugeUtil.py > b/usertools/DPDKConfigLib/GrubHugeUtil.py > new file mode 100755 > index 000000000..4b8e349b8 > --- /dev/null > +++ b/usertools/DPDKConfigLib/GrubHugeUtil.py > @@ -0,0 +1,175 @@ > +#!/usr/bin/env python > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2018 Intel Corporation > + > + > +from .PlatformInfo import * > +from .Util import * > +import re > +import os > + > +__KERNEL_TRANSPARENT_HP = "/sys/kernel/mm/transparent_hugepage/enabled" > +_GRUB_CMDLINE_PARAM_NAME = "GRUB_CMDLINE_LINUX_DEFAULT" > + > +# local copy of platform info > +info = PlatformInfo() > + > +def _find_linux_default_cmdline(): > + with open("/etc/default/grub") as f: > + for line in f: > + line = line.strip() > + if line.startswith(_GRUB_CMDLINE_PARAM_NAME): > + return line > + else: > + raise RuntimeError("Invalid GRUB default configuration format") > + > + > +def _parse_linux_default_cmdline(line): > + # get value to the right of equals sign, strip whitespace and quotes, > + # split into separate keys and make a list of values > + _, cmdline = kv_split(line, "=") > + # remove quotes > + if cmdline[0] == cmdline[-1] == '"': > + cmdline = cmdline[1:-1] > + > + return [kv_split(v, "=") for v in cmdline.split()] > + > + > +def _generate_linux_default_cmdline(cmdline): > + lines = [] > + cmdline_idx = -1 > + with open("/etc/default/grub") as f: > + for idx, line in enumerate(f): > + line = line.strip() > + lines.extend([line]) > + if line.startswith(_GRUB_CMDLINE_PARAM_NAME): > + cmdline_idx = idx > + if cmdline_idx == -1: > + raise RuntimeError("Invalid GRUB default configuration format") > + > + # write the lines back, replacing one we want > + with open("/etc/default/grub", "w") as f: > + for idx, line in enumerate(lines): > + if idx == cmdline_idx: > + line = cmdline > + f.write(line + "\n") > + > + > +def _find_transparent_hugepage(): > + if not os.path.exists(__KERNEL_TRANSPARENT_HP): > + return None > + value = read_file(__KERNEL_TRANSPARENT_HP) > + m = re.search(r"\[([a-z]+)\]", value) > + if not m: > + raise RuntimeError("BUG: Bad regular expression") > + return m.group(1) > + > + > +class GrubHugepageConfig: > + def __init__(self): > + self.update() > + > + def update(self): > + self.reset() > + > + hugepage_sizes = info.hugepage_sizes_supported > + if len(hugepage_sizes) == 0: > + raise RuntimeError("Hugepages appear to be unsupported") > + cmdline = _find_linux_default_cmdline() > + values = _parse_linux_default_cmdline(cmdline) > + > + # parse values in the list > + self.default_hugepagesz = info.default_hugepage_size > + self.transparent_hugepage = _find_transparent_hugepage() > + sizes = [] > + nrs = [] > + for k, v in values: > + if k == "default_hugepagesz": > + self.default_hugepagesz = human_readable_to_kilobytes(v) > + elif k == "transparent_hugepage": > + self.transparent_hugepage = v > + elif k == "hugepagesz": > + sizes.append(human_readable_to_kilobytes(v)) > + elif k == "hugepages": > + nrs.append(v) > + if len(sizes) != len(nrs): > + raise RuntimeError("GRUB hugepage configuration is wrong") > + detected_hugepages = dict(zip(sizes, map(int, nrs))) > + self.nr_hugepages = {size: detected_hugepages.get(size, 0) > + for size in hugepage_sizes} > + > + def commit(self): > + # perform sanity checks - we can't afford invalid data making it into > + # bootloader config, as that might render user's machine unbootable, > so > + # tread really really carefully > + > + # first, check if user didn't add any unexpected hugepage sizes > + configured_sizes = set(self.nr_hugepages.keys()) > + supported_sizes = set(info.hugepage_sizes_supported) > + > + if configured_sizes != supported_sizes: > + diff = configured_sizes.difference(supported_sizes) > + raise ValueError("Unsupported hugepage sizes: %s" % > + [kilobytes_to_human_readable(s) for s in diff]) > + > + # check if default hugepage is one of the supported ones > + if self.default_hugepagesz is not None and\ > + self.default_hugepagesz not in configured_sizes: > + s = kilobytes_to_human_readable(self.default_hugepagesz) > + raise ValueError("Unsupported default hugepage size: %i" % s) > + > + # transparent hugepages support was added in recent kernels, so check > + # if user is trying to set this > + if _find_transparent_hugepage() is None and \ > + self.transparent_hugepage is not None: > + raise ValueError("Transparent hugepages are not unsupported") > + > + # OK, parameters look to be valid - let's roll > + > + # read and parse current cmdline > + cmdline = _find_linux_default_cmdline() > + > + values = _parse_linux_default_cmdline(cmdline) > + > + # clear out old data > + klist = ["transparent_hugepage", "default_hugepagesz", > + "hugepage", "hugepagesz"] > + # iterate over a copy so that we could delete items > + for k, v in values[:]: > + if k in klist: > + values.remove((k, v)) > + > + # generate new cmdline > + cmdline = " ".join([("%s=%s" % (k, v)) if v is not None else k > + for k, v in values]) > + > + # now, populate cmdline with new data > + new_items = [] > + for sz, nr in self.nr_hugepages.items(): > + sz = kilobytes_to_human_readable(sz) > + new_items += "hugepagesz=%s hugepages=%i" % (sz, nr) > + if self.default_hugepagesz is not None: > + new_items += "default_hugepagesz=%i" % self.default_hugepagesz > + if self.transparent_hugepage is not None: > + new_items += "transparent_hugepage=%s" % > self.transparent_hugepage > + > + cmdline = "%s %s" % (cmdline, " ".join(new_items)) > + > + # strip any extraneous whitespace we may have added > + cmdline = re.sub(r"\s\s+", " ", cmdline).strip() > + > + # now, put everything back together > + cmdline = '%s="%s"' % (_GRUB_CMDLINE_PARAM_NAME, cmdline) > + > + # write it to config > + _generate_linux_default_cmdline(cmdline) > + > + # finally, update GRUB > + if not run(["update-grub"]): > + raise RuntimeError("Failed to update GRUB") > + self.update() > + > + def reset(self): > + self.nr_hugepages = {} # pagesz: number > + self.default_hugepagesz = None > + self.transparent_hugepage = None > -- > 2.17.1