291 lines
10 KiB
Python
291 lines
10 KiB
Python
"""
|
|
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")
|