| """ |
| Library to perform pre/post test setup for KVM autotest. |
| """ |
| import os, logging, time, re, random |
| 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") |
| |
| tmp_list = [] |
| test_cfg = {} |
| 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]] = re.split(":", tmp_cfg)[1] |
| # Save host current config, so we can restore it during cleanup |
| # We will only save the writeable part of the config files |
| original_config = {} |
| # List of files that contain string config values |
| self.file_list_str = [] |
| # List of files that contain integer config values |
| self.file_list_num = [] |
| for f in os.walk(self.thp_path): |
| base_dir = f[0] |
| if f[2]: |
| for name in f[2]: |
| f_dir = os.path.join(base_dir, name) |
| parameter = file(f_dir, 'r').read() |
| try: |
| # Verify if the path in question is writable |
| f = open(f_dir, 'w') |
| f.close() |
| if re.findall("\[(.*)\]", parameter): |
| original_config[f_dir] = re.findall("\[(.*)\]", |
| parameter)[0] |
| self.file_list_str.append(f_dir) |
| else: |
| original_config[f_dir] = int(parameter) |
| self.file_list_num.append(f_dir) |
| except IOError: |
| pass |
| |
| self.test_config = test_cfg |
| self.original_config = original_config |
| |
| |
| 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 value_listed(self, value): |
| """ |
| Get a parameters list from a string |
| """ |
| value_list = [] |
| for i in re.split("\[|\]|\n+|\s+", value): |
| if i: |
| value_list.append(i) |
| return value_list |
| |
| |
| def khugepaged_test(self): |
| """ |
| Start, stop and frequency change test for khugepaged. |
| """ |
| def check_status_with_value(action_list, file_name): |
| """ |
| Check the status of khugepaged when set value to specify file. |
| """ |
| for (a, r) in action_list: |
| open(file_name, "w").write(a) |
| time.sleep(5) |
| try: |
| utils.run('pgrep khugepaged') |
| if r != 0: |
| raise THPKhugepagedError("Khugepaged still alive when" |
| "transparent huge page is " |
| "disabled") |
| except error.CmdError: |
| if r == 0: |
| raise THPKhugepagedError("Khugepaged could not be set to" |
| "status %s" % a) |
| |
| |
| for file_path in self.file_list_str: |
| action_list = [] |
| if re.findall("enabled", file_path): |
| # Start and stop test for khugepaged |
| value_list = self.value_listed(open(file_path,"r").read()) |
| for i in value_list: |
| if re.match("n", i, re.I): |
| action_stop = (i, 256) |
| for i in value_list: |
| if re.match("[^n]", i, re.I): |
| action = (i, 0) |
| action_list += [action_stop, action, action_stop] |
| action_list += [action] |
| |
| check_status_with_value(action_list, file_path) |
| else: |
| value_list = self.value_listed(open(file_path,"r").read()) |
| for i in value_list: |
| action = (i, 0) |
| action_list.append(action) |
| check_status_with_value(action_list, file_path) |
| |
| for file_path in self.file_list_num: |
| action_list = [] |
| value = int(open(file_path, "r").read()) |
| if value != 0 and value != 1: |
| new_value = random.random() |
| action_list.append((str(int(value * new_value)),0)) |
| action_list.append((str(int(value * ( new_value + 1))),0)) |
| else: |
| action_list.append(("0", 0)) |
| action_list.append(("1", 0)) |
| |
| check_status_with_value(action_list, file_path) |
| |
| |
| 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(str(self.original_config[path])) |
| p_file.close() |
| |
| |
| class HugePageConfig(object): |
| def __init__(self, params): |
| """ |
| Gets environment variable values and calculates the target number |
| of huge memory pages. |
| |
| @param params: Dict like object containing parameters for the test. |
| """ |
| self.vms = len(params.objects("vms")) |
| self.mem = int(params.get("mem")) |
| self.max_vms = int(params.get("max_vms", 0)) |
| self.hugepage_path = '/mnt/kvm_hugepage' |
| self.hugepage_size = self.get_hugepage_size() |
| self.target_hugepages = self.get_target_hugepages() |
| self.kernel_hp_file = '/proc/sys/vm/nr_hugepages' |
| |
| |
| def get_hugepage_size(self): |
| """ |
| Get the current system setting for huge memory page size. |
| """ |
| meminfo = open('/proc/meminfo', 'r').readlines() |
| huge_line_list = [h for h in meminfo if h.startswith("Hugepagesize")] |
| try: |
| return int(huge_line_list[0].split()[1]) |
| except ValueError, e: |
| raise ValueError("Could not get huge page size setting from " |
| "/proc/meminfo: %s" % e) |
| |
| |
| def get_target_hugepages(self): |
| """ |
| Calculate the target number of hugepages for testing purposes. |
| """ |
| if self.vms < self.max_vms: |
| self.vms = self.max_vms |
| # memory of all VMs plus qemu overhead of 64MB per guest |
| vmsm = (self.vms * self.mem) + (self.vms * 64) |
| return int(vmsm * 1024 / self.hugepage_size) |
| |
| |
| @error.context_aware |
| def set_hugepages(self): |
| """ |
| Sets the hugepage limit to the target hugepage value calculated. |
| """ |
| error.context("setting hugepages limit to %s" % self.target_hugepages) |
| hugepage_cfg = open(self.kernel_hp_file, "r+") |
| hp = hugepage_cfg.readline() |
| while int(hp) < self.target_hugepages: |
| loop_hp = hp |
| hugepage_cfg.write(str(self.target_hugepages)) |
| hugepage_cfg.flush() |
| hugepage_cfg.seek(0) |
| hp = int(hugepage_cfg.readline()) |
| if loop_hp == hp: |
| raise ValueError("Cannot set the kernel hugepage setting " |
| "to the target value of %d hugepages." % |
| self.target_hugepages) |
| hugepage_cfg.close() |
| logging.debug("Successfuly set %s large memory pages on host ", |
| self.target_hugepages) |
| |
| |
| @error.context_aware |
| def mount_hugepage_fs(self): |
| """ |
| Verify if there's a hugetlbfs mount set. If there's none, will set up |
| a hugetlbfs mount using the class attribute that defines the mount |
| point. |
| """ |
| error.context("mounting hugepages path") |
| if not os.path.ismount(self.hugepage_path): |
| if not os.path.isdir(self.hugepage_path): |
| os.makedirs(self.hugepage_path) |
| cmd = "mount -t hugetlbfs none %s" % self.hugepage_path |
| utils.system(cmd) |
| |
| |
| def setup(self): |
| logging.debug("Number of VMs this test will use: %d", self.vms) |
| logging.debug("Amount of memory used by each vm: %s", self.mem) |
| logging.debug("System setting for large memory page size: %s", |
| self.hugepage_size) |
| logging.debug("Number of large memory pages needed for this test: %s", |
| self.target_hugepages) |
| self.set_hugepages() |
| self.mount_hugepage_fs() |
| |
| |
| @error.context_aware |
| def cleanup(self): |
| error.context("trying to dealocate hugepage memory") |
| try: |
| utils.system("umount %s" % self.hugepage_path) |
| except error.CmdError: |
| return |
| utils.system("echo 0 > %s" % self.kernel_hp_file) |
| logging.debug("Hugepage memory successfuly dealocated") |