Home | History | Annotate | Download | only in virt
      1 """
      2 Library to perform pre/post test setup for KVM autotest.
      3 """
      4 import os, logging, time, re, random
      5 from autotest_lib.client.common_lib import error
      6 from autotest_lib.client.bin import utils
      7 
      8 
      9 class THPError(Exception):
     10     """
     11     Base exception for Transparent Hugepage setup.
     12     """
     13     pass
     14 
     15 
     16 class THPNotSupportedError(THPError):
     17     """
     18     Thrown when host does not support tansparent hugepages.
     19     """
     20     pass
     21 
     22 
     23 class THPWriteConfigError(THPError):
     24     """
     25     Thrown when host does not support tansparent hugepages.
     26     """
     27     pass
     28 
     29 
     30 class THPKhugepagedError(THPError):
     31     """
     32     Thrown when khugepaged is not behaving as expected.
     33     """
     34     pass
     35 
     36 
     37 class TransparentHugePageConfig(object):
     38     def __init__(self, test, params):
     39         """
     40         Find paths for transparent hugepages and kugepaged configuration. Also,
     41         back up original host configuration so it can be restored during
     42         cleanup.
     43         """
     44         self.params = params
     45 
     46         RH_THP_PATH = "/sys/kernel/mm/redhat_transparent_hugepage"
     47         UPSTREAM_THP_PATH = "/sys/kernel/mm/transparent_hugepage"
     48         if os.path.isdir(RH_THP_PATH):
     49             self.thp_path = RH_THP_PATH
     50         elif os.path.isdir(UPSTREAM_THP_PATH):
     51             self.thp_path = UPSTREAM_THP_PATH
     52         else:
     53             raise THPNotSupportedError("System doesn't support transparent "
     54                                        "hugepages")
     55 
     56         tmp_list = []
     57         test_cfg = {}
     58         test_config = self.params.get("test_config", None)
     59         if test_config is not None:
     60             tmp_list = re.split(';', test_config)
     61         while len(tmp_list) > 0:
     62             tmp_cfg = tmp_list.pop()
     63             test_cfg[re.split(":", tmp_cfg)[0]] = re.split(":", tmp_cfg)[1]
     64         # Save host current config, so we can restore it during cleanup
     65         # We will only save the writeable part of the config files
     66         original_config = {}
     67         # List of files that contain string config values
     68         self.file_list_str = []
     69         # List of files that contain integer config values
     70         self.file_list_num = []
     71         for f in os.walk(self.thp_path):
     72             base_dir = f[0]
     73             if f[2]:
     74                 for name in f[2]:
     75                     f_dir = os.path.join(base_dir, name)
     76                     parameter = file(f_dir, 'r').read()
     77                     try:
     78                         # Verify if the path in question is writable
     79                         f = open(f_dir, 'w')
     80                         f.close()
     81                         if re.findall("\[(.*)\]", parameter):
     82                             original_config[f_dir] = re.findall("\[(.*)\]",
     83                                                            parameter)[0]
     84                             self.file_list_str.append(f_dir)
     85                         else:
     86                             original_config[f_dir] = int(parameter)
     87                             self.file_list_num.append(f_dir)
     88                     except IOError:
     89                         pass
     90 
     91         self.test_config = test_cfg
     92         self.original_config = original_config
     93 
     94 
     95     def set_env(self):
     96         """
     97         Applies test configuration on the host.
     98         """
     99         if self.test_config:
    100             for path in self.test_config.keys():
    101                 file(path, 'w').write(self.test_config[path])
    102 
    103 
    104     def value_listed(self, value):
    105         """
    106         Get a parameters list from a string
    107         """
    108         value_list = []
    109         for i in re.split("\[|\]|\n+|\s+", value):
    110             if i:
    111                 value_list.append(i)
    112         return value_list
    113 
    114 
    115     def khugepaged_test(self):
    116         """
    117         Start, stop and frequency change test for khugepaged.
    118         """
    119         def check_status_with_value(action_list, file_name):
    120             """
    121             Check the status of khugepaged when set value to specify file.
    122             """
    123             for (a, r) in action_list:
    124                 open(file_name, "w").write(a)
    125                 time.sleep(5)
    126                 try:
    127                     utils.run('pgrep khugepaged')
    128                     if r != 0:
    129                         raise THPKhugepagedError("Khugepaged still alive when"
    130                                                  "transparent huge page is "
    131                                                  "disabled")
    132                 except error.CmdError:
    133                     if r == 0:
    134                         raise THPKhugepagedError("Khugepaged could not be set to"
    135                                                  "status %s" % a)
    136 
    137 
    138         for file_path in self.file_list_str:
    139             action_list = []
    140             if re.findall("enabled", file_path):
    141                 # Start and stop test for khugepaged
    142                 value_list = self.value_listed(open(file_path,"r").read())
    143                 for i in value_list:
    144                     if re.match("n", i, re.I):
    145                         action_stop = (i, 256)
    146                 for i in value_list:
    147                     if re.match("[^n]", i, re.I):
    148                         action = (i, 0)
    149                         action_list += [action_stop, action, action_stop]
    150                 action_list += [action]
    151 
    152                 check_status_with_value(action_list, file_path)
    153             else:
    154                 value_list = self.value_listed(open(file_path,"r").read())
    155                 for i in value_list:
    156                     action = (i, 0)
    157                     action_list.append(action)
    158                 check_status_with_value(action_list, file_path)
    159 
    160         for file_path in self.file_list_num:
    161             action_list = []
    162             value = int(open(file_path, "r").read())
    163             if value != 0 and value != 1:
    164                 new_value = random.random()
    165                 action_list.append((str(int(value * new_value)),0))
    166                 action_list.append((str(int(value * ( new_value + 1))),0))
    167             else:
    168                 action_list.append(("0", 0))
    169                 action_list.append(("1", 0))
    170 
    171             check_status_with_value(action_list, file_path)
    172 
    173 
    174     def setup(self):
    175         """
    176         Configure host for testing. Also, check that khugepaged is working as
    177         expected.
    178         """
    179         self.set_env()
    180         self.khugepaged_test()
    181 
    182 
    183     def cleanup(self):
    184         """:
    185         Restore the host's original configuration after test
    186         """
    187         for path in self.original_config:
    188             p_file = open(path, 'w')
    189             p_file.write(str(self.original_config[path]))
    190             p_file.close()
    191 
    192 
    193 class HugePageConfig(object):
    194     def __init__(self, params):
    195         """
    196         Gets environment variable values and calculates the target number
    197         of huge memory pages.
    198 
    199         @param params: Dict like object containing parameters for the test.
    200         """
    201         self.vms = len(params.objects("vms"))
    202         self.mem = int(params.get("mem"))
    203         self.max_vms = int(params.get("max_vms", 0))
    204         self.hugepage_path = '/mnt/kvm_hugepage'
    205         self.hugepage_size = self.get_hugepage_size()
    206         self.target_hugepages = self.get_target_hugepages()
    207         self.kernel_hp_file = '/proc/sys/vm/nr_hugepages'
    208 
    209 
    210     def get_hugepage_size(self):
    211         """
    212         Get the current system setting for huge memory page size.
    213         """
    214         meminfo = open('/proc/meminfo', 'r').readlines()
    215         huge_line_list = [h for h in meminfo if h.startswith("Hugepagesize")]
    216         try:
    217             return int(huge_line_list[0].split()[1])
    218         except ValueError, e:
    219             raise ValueError("Could not get huge page size setting from "
    220                              "/proc/meminfo: %s" % e)
    221 
    222 
    223     def get_target_hugepages(self):
    224         """
    225         Calculate the target number of hugepages for testing purposes.
    226         """
    227         if self.vms < self.max_vms:
    228             self.vms = self.max_vms
    229         # memory of all VMs plus qemu overhead of 64MB per guest
    230         vmsm = (self.vms * self.mem) + (self.vms * 64)
    231         return int(vmsm * 1024 / self.hugepage_size)
    232 
    233 
    234     @error.context_aware
    235     def set_hugepages(self):
    236         """
    237         Sets the hugepage limit to the target hugepage value calculated.
    238         """
    239         error.context("setting hugepages limit to %s" % self.target_hugepages)
    240         hugepage_cfg = open(self.kernel_hp_file, "r+")
    241         hp = hugepage_cfg.readline()
    242         while int(hp) < self.target_hugepages:
    243             loop_hp = hp
    244             hugepage_cfg.write(str(self.target_hugepages))
    245             hugepage_cfg.flush()
    246             hugepage_cfg.seek(0)
    247             hp = int(hugepage_cfg.readline())
    248             if loop_hp == hp:
    249                 raise ValueError("Cannot set the kernel hugepage setting "
    250                                  "to the target value of %d hugepages." %
    251                                  self.target_hugepages)
    252         hugepage_cfg.close()
    253         logging.debug("Successfuly set %s large memory pages on host ",
    254                       self.target_hugepages)
    255 
    256 
    257     @error.context_aware
    258     def mount_hugepage_fs(self):
    259         """
    260         Verify if there's a hugetlbfs mount set. If there's none, will set up
    261         a hugetlbfs mount using the class attribute that defines the mount
    262         point.
    263         """
    264         error.context("mounting hugepages path")
    265         if not os.path.ismount(self.hugepage_path):
    266             if not os.path.isdir(self.hugepage_path):
    267                 os.makedirs(self.hugepage_path)
    268             cmd = "mount -t hugetlbfs none %s" % self.hugepage_path
    269             utils.system(cmd)
    270 
    271 
    272     def setup(self):
    273         logging.debug("Number of VMs this test will use: %d", self.vms)
    274         logging.debug("Amount of memory used by each vm: %s", self.mem)
    275         logging.debug("System setting for large memory page size: %s",
    276                       self.hugepage_size)
    277         logging.debug("Number of large memory pages needed for this test: %s",
    278                       self.target_hugepages)
    279         self.set_hugepages()
    280         self.mount_hugepage_fs()
    281 
    282 
    283     @error.context_aware
    284     def cleanup(self):
    285         error.context("trying to dealocate hugepage memory")
    286         try:
    287             utils.system("umount %s" % self.hugepage_path)
    288         except error.CmdError:
    289             return
    290         utils.system("echo 0 > %s" % self.kernel_hp_file)
    291         logging.debug("Hugepage memory successfuly dealocated")
    292