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