diff mbox

[1/3] virt: Add Transparent Hugepages setup v2

Message ID 1308183516-5247-2-git-send-email-lmr@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lucas Meneghel Rodrigues June 16, 2011, 12:18 a.m. UTC
This class configures khugepaged to active mode, with
functions to restore original guest configuration.

Changes from v1:
* Rather than a pre/post script, config is now part of
the framework
* No need to store configuration in files anymore to restore
host khugepaged original behavior

Signed-off-by: Yiqiao Pu <ypu@redhat.com
Signed-off-by: Lucas Meneghel Rodrigues <lmr@redhat.com>
---
 client/virt/virt_test_setup.py |  143 +++++++++++++++++++++++++++++++++++++++-
 1 files changed, 142 insertions(+), 1 deletions(-)

Comments

Andrea Arcangeli June 16, 2011, 3:56 p.m. UTC | #1
Hi Lucas,

On Wed, Jun 15, 2011 at 09:18:34PM -0300, Lucas Meneghel Rodrigues wrote:
> +class THPNotSupportedError(THPError):
> +    """
> +    Thrown when host does not support tansparent hugepages.
> +    """
> +    pass

s/tansparent/transparent/

> +class THPWriteConfigError(THPError):
> +    """
> +    Thrown when host does not support tansparent hugepages.
> +    """
> +    pass

same as above.

> +class TransparentHugePageConfig(object):
> +    def __init__(self, test, params):
> +        """
> +        Find paths for transparent hugepages and kugepaged configuration. Also,
> +        back up original host configuration so it can be restored during
> +        cleanup.
> +        """
> +        self.params = params
> +
> +        RH_THP_PATH = "/sys/kernel/mm/redhat_transparent_hugepage"
> +        UPSTREAM_THP_PATH = "/sys/kernel/mm/transparent_hugepage"
> +        if os.path.isdir(RH_THP_PATH):
> +            self.thp_path = RH_THP_PATH
> +        elif os.path.isdir(UPSTREAM_THP_PATH):
> +            self.thp_path = UPSTREAM_THP_PATH
> +        else:
> +            raise THPNotSupportedError("System doesn't support transparent "
> +                                       "hugepages")
> +
> +        # test_cfg holds all the desired host config values we want to set
> +        # before THP tests
> +        test_cfg={"%s/defrag" % self.thp_path: "yes",

upstream >=3.0.0 this would value would be 1 (on 2.6.38 it's still "yes").

> +                  "%s/enabled" % self.thp_path: "always",
> +                  "%s/khugepaged/defrag" % self.thp_path: "yes",

Upstream >=3.0.0 it's 1.

> +                  "%s/khugepaged/scan_sleep_millisecs" % self.thp_path: "100",

So this is meant to increase khugepaged scan rate compared to the
default.

> +                  "%s/khugepaged/pages_to_scan" % self.thp_path: "4096",
> +                  "%s/khugepaged/alloc_sleep_millisecs" % self.thp_path: "100",

An allocation failure is not very likely and it's generally better to
keep this bigger than the scan_sleep_millisecs. I would keep a factor
of 6 of difference, so you could set it to 600 if scan_sleep_millisecs
is set to 100.

> +                  "/sys/kernel/mm/ksm/run": "1",
> +                  "/proc/sys/vm/nr_hugepages":"0"}

nr_hugepages is unlikely to matter.

> +        if os.path.isfile("%s/khugepaged/enabled" % self.thp_path):
> +            test_cfg["%s/khugepaged/enabled" % self.thp_path] = "always"
> +        if os.path.isfile("%s/khugepaged/max_ptes_none" % self.thp_path):
> +            test_cfg["%s/khugepaged/max_ptes_none" % self.thp_path] = "511"
> +            test_cfg["%s/defrag" % self.thp_path] = "always"
> +
> +        tmp_list = []
> +        test_config = self.params.get("test_config", None)
> +        if test_config is not None:
> +            tmp_list = re.split(';', test_config)
> +        while len(tmp_list) > 0:
> +            tmp_cfg = tmp_list.pop()
> +            test_cfg[re.split(":", tmp_cfg)[0]] = sre.split(":", tmp_cfg)[1]
> +
> +        self.original_config = {}
> +        # Save host current config, so we can restore it during cleanup
> +        for path in test_cfg:
> +            param = open(path, 'r').read()
> +            if ("enabled" in param) or ("defrag" in param):
> +                param = re.split("\[|\]", param)[1] + '\n'
> +            self.original_config[path] = param
> +
> +        self.test_config = test_cfg
> +
> +
> +    def set_env(self):
> +        """
> +        Applies test configuration on the host.
> +        """
> +        if self.test_config:
> +            for path in self.test_config.keys():
> +                file(path, 'w').write(self.test_config[path])
> +
> +
> +    def set_params(self, path_dict={}, filename="", value=""):
> +        """
> +        Sets the value of a config file for a given path.
> +
> +        @param path_dict: Dict of files' paths {path : value}
> +        @param filename: Name of file to setup
> +        @param value: Value set to the configuration files
> +        """
> +        for path in path_dict.keys():
> +            if path in filename:
> +                try:
> +                    file(path, "w").write(value)
> +                except IOError, e:
> +                    raise THPWriteConfigError("Can not set %s to %s: %s" %
> +                                              (value, filename, e))
> +
> +
> +    def khugepaged_test(self):
> +        """
> +        Start, stop and frequency change test for khugepaged.
> +        """
> +        status_list = ["never", "always", "never"]
> +        for status in status_list:
> +            self.set_params(self.test_config, "enabled", status)
> +            try:
> +                utils.run('pgrep khugepaged')
> +            except error.CmdError:
> +                raise THPKhugepagedError("khugepaged can not be set to "
> +                                         "status %s" % status)

khugepaged will quit when enabled is set to never, but it's not a bug
(it's to save the memory of the kernel thread stack when THP is
disabled). So I'm unsure if the pgrep is going to create spurious errors.
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Lucas Meneghel Rodrigues June 16, 2011, 4:34 p.m. UTC | #2
On Thu, 2011-06-16 at 17:56 +0200, Andrea Arcangeli wrote:
> Hi Lucas,

Hi Andrea, thanks for the review! Yiqiao is working on the patchset, v3
or v4 will contain the fixes you have pointed out. 

> > +        # test_cfg holds all the desired host config values we want to set
> > +        # before THP tests
> > +        test_cfg={"%s/defrag" % self.thp_path: "yes",
> 
> upstream >=3.0.0 this would value would be 1 (on 2.6.38 it's still "yes").
> 
> > +                  "%s/enabled" % self.thp_path: "always",
> > +                  "%s/khugepaged/defrag" % self.thp_path: "yes",
> 
> Upstream >=3.0.0 it's 1.

Ok, we'll set the values conditionally depending on the running kernel
version.

> > +                  "%s/khugepaged/scan_sleep_millisecs" % self.thp_path: "100",
> 
> So this is meant to increase khugepaged scan rate compared to the
> default.

Yep.

> > +                  "%s/khugepaged/pages_to_scan" % self.thp_path: "4096",
> > +                  "%s/khugepaged/alloc_sleep_millisecs" % self.thp_path: "100",
> 
> An allocation failure is not very likely and it's generally better to
> keep this bigger than the scan_sleep_millisecs. I would keep a factor
> of 6 of difference, so you could set it to 600 if scan_sleep_millisecs
> is set to 100.

Ok, will do.

> > +                  "/sys/kernel/mm/ksm/run": "1",
> > +                  "/proc/sys/vm/nr_hugepages":"0"}
> 
> nr_hugepages is unlikely to matter.

Dropping this then.

> > +        if os.path.isfile("%s/khugepaged/enabled" % self.thp_path):
> > +            test_cfg["%s/khugepaged/enabled" % self.thp_path] = "always"
> > +        if os.path.isfile("%s/khugepaged/max_ptes_none" % self.thp_path):
> > +            test_cfg["%s/khugepaged/max_ptes_none" % self.thp_path] = "511"
> > +            test_cfg["%s/defrag" % self.thp_path] = "always"
> > +
> > +        tmp_list = []
> > +        test_config = self.params.get("test_config", None)
> > +        if test_config is not None:
> > +            tmp_list = re.split(';', test_config)
> > +        while len(tmp_list) > 0:
> > +            tmp_cfg = tmp_list.pop()
> > +            test_cfg[re.split(":", tmp_cfg)[0]] = sre.split(":", tmp_cfg)[1]
> > +
> > +        self.original_config = {}
> > +        # Save host current config, so we can restore it during cleanup
> > +        for path in test_cfg:
> > +            param = open(path, 'r').read()
> > +            if ("enabled" in param) or ("defrag" in param):
> > +                param = re.split("\[|\]", param)[1] + '\n'
> > +            self.original_config[path] = param
> > +
> > +        self.test_config = test_cfg
> > +
> > +
> > +    def set_env(self):
> > +        """
> > +        Applies test configuration on the host.
> > +        """
> > +        if self.test_config:
> > +            for path in self.test_config.keys():
> > +                file(path, 'w').write(self.test_config[path])
> > +
> > +
> > +    def set_params(self, path_dict={}, filename="", value=""):
> > +        """
> > +        Sets the value of a config file for a given path.
> > +
> > +        @param path_dict: Dict of files' paths {path : value}
> > +        @param filename: Name of file to setup
> > +        @param value: Value set to the configuration files
> > +        """
> > +        for path in path_dict.keys():
> > +            if path in filename:
> > +                try:
> > +                    file(path, "w").write(value)
> > +                except IOError, e:
> > +                    raise THPWriteConfigError("Can not set %s to %s: %s" %
> > +                                              (value, filename, e))
> > +
> > +
> > +    def khugepaged_test(self):
> > +        """
> > +        Start, stop and frequency change test for khugepaged.
> > +        """
> > +        status_list = ["never", "always", "never"]
> > +        for status in status_list:
> > +            self.set_params(self.test_config, "enabled", status)
> > +            try:
> > +                utils.run('pgrep khugepaged')
> > +            except error.CmdError:
> > +                raise THPKhugepagedError("khugepaged can not be set to "
> > +                                         "status %s" % status)
> 
> khugepaged will quit when enabled is set to never, but it's not a bug
> (it's to save the memory of the kernel thread stack when THP is
> disabled). So I'm unsure if the pgrep is going to create spurious errors.

Ok, will improve this check then, based on the expected behavior.

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Andrea Arcangeli June 17, 2011, 8:21 a.m. UTC | #3
On Thu, Jun 16, 2011 at 01:34:54PM -0300, Lucas Meneghel Rodrigues wrote:
> On Thu, 2011-06-16 at 17:56 +0200, Andrea Arcangeli wrote:
> > Hi Lucas,
> 
> Hi Andrea, thanks for the review! Yiqiao is working on the patchset, v3
> or v4 will contain the fixes you have pointed out. 

Thanks! BTW, I'm in the process of reviewing the other patches too.

> 
> > > +        # test_cfg holds all the desired host config values we want to set
> > > +        # before THP tests
> > > +        test_cfg={"%s/defrag" % self.thp_path: "yes",
> > 
> > upstream >=3.0.0 this would value would be 1 (on 2.6.38 it's still "yes").
> > 
> > > +                  "%s/enabled" % self.thp_path: "always",
> > > +                  "%s/khugepaged/defrag" % self.thp_path: "yes",
> > 
> > Upstream >=3.0.0 it's 1.
> 
> Ok, we'll set the values conditionally depending on the running kernel
> version.

Checking the kernel version may be less reliable than reading it back
and seeing if it's yes|no or 0|1 (not sure how the stable kernels will
work with 3.0.0 etc..), the future numbering seems a bit variable
these days. Theoretically checking the version number is better, but I
suspect reading it and handling both formats without regard of the
kernel version is simpler in the end. But it's definitely up to you.

> > > +                  "%s/khugepaged/scan_sleep_millisecs" % self.thp_path: "100",
> > 
> > So this is meant to increase khugepaged scan rate compared to the
> > default.
> 
> Yep.

Sounds good!

> > > +                  "/sys/kernel/mm/ksm/run": "1",
> > > +                  "/proc/sys/vm/nr_hugepages":"0"}
> > 
> > nr_hugepages is unlikely to matter.
> 
> Dropping this then.

I think you can drop it yes. I think this only could cause hugetlbfs
allocation failures if there's some VM backed by hugetlbfs pages that
could then fail allocation and they have no fallback like THP.

hugetlbfs and THP are completely separated handled, even if they share
plenty code for the clear-copy and
put_page/get_page/head/tail/compound logics.

> > > +    def khugepaged_test(self):
> > > +        """
> > > +        Start, stop and frequency change test for khugepaged.
> > > +        """
> > > +        status_list = ["never", "always", "never"]
> > > +        for status in status_list:
> > > +            self.set_params(self.test_config, "enabled", status)
> > > +            try:
> > > +                utils.run('pgrep khugepaged')
> > > +            except error.CmdError:
> > > +                raise THPKhugepagedError("khugepaged can not be set to "
> > > +                                         "status %s" % status)
> > 
> > khugepaged will quit when enabled is set to never, but it's not a bug
> > (it's to save the memory of the kernel thread stack when THP is
> > disabled). So I'm unsure if the pgrep is going to create spurious errors.
> 
> Ok, will improve this check then, based on the expected behavior.

If you wait a few seconds (a few seconds should be enough even with
heavy loads) khugepaged should disappear after setting enabled=never,
and again in a few seconds it should appear after setting
enabled=always.

So when setting enabled=never, you can wait with a timeout until
pgrep throws the CmdError as expected, and print an error if it pgrep
keeps succeeding. The opposite when setting enabled=always.

This is a bit kernel dependent (for example other kernel daemons
always are present even when the feature is disabled, I handled the
races in shutting down and turning on the daemon by optimizing it
more, but it's not very important to check that the khugepaged goes
away, so you may just limit the pgrep check to when enabled=always to
verify khugepaged is there, but you should wait a few seconds before
failing as it may take a reschedule for khugepaged to start).

Thanks,
Andrea
--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/client/virt/virt_test_setup.py b/client/virt/virt_test_setup.py
index 3e1f5b5..792ffe6 100644
--- a/client/virt/virt_test_setup.py
+++ b/client/virt/virt_test_setup.py
@@ -1,11 +1,152 @@ 
 """
 Library to perform pre/post test setup for KVM autotest.
 """
-import os, logging
+import os, logging, re, sre
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.bin import utils
 
 
+class THPError(Exception):
+    """
+    Base exception for Transparent Hugepage setup.
+    """
+    pass
+
+
+class THPNotSupportedError(THPError):
+    """
+    Thrown when host does not support tansparent hugepages.
+    """
+    pass
+
+
+class THPWriteConfigError(THPError):
+    """
+    Thrown when host does not support tansparent hugepages.
+    """
+    pass
+
+
+class THPKhugepagedError(THPError):
+    """
+    Thrown when khugepaged is not behaving as expected.
+    """
+    pass
+
+
+class TransparentHugePageConfig(object):
+    def __init__(self, test, params):
+        """
+        Find paths for transparent hugepages and kugepaged configuration. Also,
+        back up original host configuration so it can be restored during
+        cleanup.
+        """
+        self.params = params
+
+        RH_THP_PATH = "/sys/kernel/mm/redhat_transparent_hugepage"
+        UPSTREAM_THP_PATH = "/sys/kernel/mm/transparent_hugepage"
+        if os.path.isdir(RH_THP_PATH):
+            self.thp_path = RH_THP_PATH
+        elif os.path.isdir(UPSTREAM_THP_PATH):
+            self.thp_path = UPSTREAM_THP_PATH
+        else:
+            raise THPNotSupportedError("System doesn't support transparent "
+                                       "hugepages")
+
+        # test_cfg holds all the desired host config values we want to set
+        # before THP tests
+        test_cfg={"%s/defrag" % self.thp_path: "yes",
+                  "%s/enabled" % self.thp_path: "always",
+                  "%s/khugepaged/defrag" % self.thp_path: "yes",
+                  "%s/khugepaged/scan_sleep_millisecs" % self.thp_path: "100",
+                  "%s/khugepaged/pages_to_scan" % self.thp_path: "4096",
+                  "%s/khugepaged/alloc_sleep_millisecs" % self.thp_path: "100",
+                  "/sys/kernel/mm/ksm/run": "1",
+                  "/proc/sys/vm/nr_hugepages":"0"}
+        if os.path.isfile("%s/khugepaged/enabled" % self.thp_path):
+            test_cfg["%s/khugepaged/enabled" % self.thp_path] = "always"
+        if os.path.isfile("%s/khugepaged/max_ptes_none" % self.thp_path):
+            test_cfg["%s/khugepaged/max_ptes_none" % self.thp_path] = "511"
+            test_cfg["%s/defrag" % self.thp_path] = "always"
+
+        tmp_list = []
+        test_config = self.params.get("test_config", None)
+        if test_config is not None:
+            tmp_list = re.split(';', test_config)
+        while len(tmp_list) > 0:
+            tmp_cfg = tmp_list.pop()
+            test_cfg[re.split(":", tmp_cfg)[0]] = sre.split(":", tmp_cfg)[1]
+
+        self.original_config = {}
+        # Save host current config, so we can restore it during cleanup
+        for path in test_cfg:
+            param = open(path, 'r').read()
+            if ("enabled" in param) or ("defrag" in param):
+                param = re.split("\[|\]", param)[1] + '\n'
+            self.original_config[path] = param
+
+        self.test_config = test_cfg
+
+
+    def set_env(self):
+        """
+        Applies test configuration on the host.
+        """
+        if self.test_config:
+            for path in self.test_config.keys():
+                file(path, 'w').write(self.test_config[path])
+
+
+    def set_params(self, path_dict={}, filename="", value=""):
+        """
+        Sets the value of a config file for a given path.
+
+        @param path_dict: Dict of files' paths {path : value}
+        @param filename: Name of file to setup
+        @param value: Value set to the configuration files
+        """
+        for path in path_dict.keys():
+            if path in filename:
+                try:
+                    file(path, "w").write(value)
+                except IOError, e:
+                    raise THPWriteConfigError("Can not set %s to %s: %s" %
+                                              (value, filename, e))
+
+
+    def khugepaged_test(self):
+        """
+        Start, stop and frequency change test for khugepaged.
+        """
+        status_list = ["never", "always", "never"]
+        for status in status_list:
+            self.set_params(self.test_config, "enabled", status)
+            try:
+                utils.run('pgrep khugepaged')
+            except error.CmdError:
+                raise THPKhugepagedError("khugepaged can not be set to "
+                                         "status %s" % status)
+
+
+    def setup(self):
+        """
+        Configure host for testing. Also, check that khugepaged is working as
+        expected.
+        """
+        self.set_env()
+        self.khugepaged_test()
+
+
+    def cleanup(self):
+        """:
+        Restore the host's original configuration after test
+        """
+        for path in self.original_config:
+            p_file = open(path, 'w')
+            p_file.write(self.original_config[path])
+            p_file.close()
+
+
 class HugePageConfig(object):
     def __init__(self, params):
         """