blob: c3b4cad4cad372a547b2e9f3cf71e869cf610e5d [file] [log] [blame]
import logging, time, random, math, os
from autotest_lib.client.common_lib import error
from autotest_lib.client.bin import utils
from autotest_lib.client.virt import virt_utils, virt_test_utils, aexpect
from autotest_lib.client.virt import virt_env_process
def run_ksm_overcommit(test, params, env):
"""
Test how KSM (Kernel Shared Memory) act when more than physical memory is
used. In second part we also test how KVM handles a situation when the host
runs out of memory (it is expected to pause the guest system, wait until
some process returns memory and bring the guest back to life)
@param test: kvm test object.
@param params: Dictionary with test parameters.
@param env: Dictionary with the test wnvironment.
"""
def _start_allocator(vm, session, timeout):
"""
Execute ksm_overcommit_guest.py on a guest, wait until it is initialized.
@param vm: VM object.
@param session: Remote session to a VM object.
@param timeout: Timeout that will be used to verify if
ksm_overcommit_guest.py started properly.
"""
logging.debug("Starting ksm_overcommit_guest.py on guest %s", vm.name)
session.sendline("python /tmp/ksm_overcommit_guest.py")
try:
session.read_until_last_line_matches(["PASS:", "FAIL:"], timeout)
except aexpect.ExpectProcessTerminatedError, e:
e_msg = ("Command ksm_overcommit_guest.py on vm '%s' failed: %s" %
(vm.name, str(e)))
raise error.TestFail(e_msg)
def _execute_allocator(command, vm, session, timeout):
"""
Execute a given command on ksm_overcommit_guest.py main loop,
indicating the vm the command was executed on.
@param command: Command that will be executed.
@param vm: VM object.
@param session: Remote session to VM object.
@param timeout: Timeout used to verify expected output.
@return: Tuple (match index, data)
"""
logging.debug("Executing '%s' on ksm_overcommit_guest.py loop, "
"vm: %s, timeout: %s", command, vm.name, timeout)
session.sendline(command)
try:
(match, data) = session.read_until_last_line_matches(
["PASS:","FAIL:"],
timeout)
except aexpect.ExpectProcessTerminatedError, e:
e_msg = ("Failed to execute command '%s' on "
"ksm_overcommit_guest.py, vm '%s': %s" %
(command, vm.name, str(e)))
raise error.TestFail(e_msg)
return (match, data)
def get_ksmstat():
"""
Return sharing memory by ksm in MB
@return: memory in MB
"""
f = open('/sys/kernel/mm/ksm/pages_sharing')
ksm_pages = int(f.read())
f.close()
return ((ksm_pages*4096)/1e6)
def initialize_guests():
"""
Initialize guests (fill their memories with specified patterns).
"""
logging.info("Phase 1: filling guest memory pages")
for session in lsessions:
vm = lvms[lsessions.index(session)]
logging.debug("Turning off swap on vm %s", vm.name)
session.cmd("swapoff -a", timeout=300)
# Start the allocator
_start_allocator(vm, session, 60 * perf_ratio)
# Execute allocator on guests
for i in range(0, vmsc):
vm = lvms[i]
a_cmd = "mem = MemFill(%d, %s, %s)" % (ksm_size, skeys[i], dkeys[i])
_execute_allocator(a_cmd, vm, lsessions[i], 60 * perf_ratio)
a_cmd = "mem.value_fill(%d)" % skeys[0]
_execute_allocator(a_cmd, vm, lsessions[i], 120 * perf_ratio)
# Let ksm_overcommit_guest.py do its job
# (until shared mem reaches expected value)
shm = 0
j = 0
logging.debug("Target shared meminfo for guest %s: %s", vm.name,
ksm_size)
while ((new_ksm and (shm < (ksm_size*(i+1)))) or
(not new_ksm and (shm < (ksm_size)))):
if j > 64:
logging.debug(virt_test_utils.get_memory_info(lvms))
raise error.TestError("SHM didn't merge the memory until "
"the DL on guest: %s" % vm.name)
st = ksm_size / 200 * perf_ratio
logging.debug("Waiting %ds before proceeding...", st)
time.sleep(st)
if (new_ksm):
shm = get_ksmstat()
else:
shm = vm.get_shared_meminfo()
logging.debug("Shared meminfo for guest %s after "
"iteration %s: %s", vm.name, j, shm)
j += 1
# Keep some reserve
rt = ksm_size / 200 * perf_ratio
logging.debug("Waiting %ds before proceeding...", rt)
time.sleep(rt)
logging.debug(virt_test_utils.get_memory_info(lvms))
logging.info("Phase 1: PASS")
def separate_first_guest():
"""
Separate memory of the first guest by generating special random series
"""
logging.info("Phase 2: Split the pages on the first guest")
a_cmd = "mem.static_random_fill()"
data = _execute_allocator(a_cmd, lvms[0], lsessions[0],
120 * perf_ratio)[1]
r_msg = data.splitlines()[-1]
logging.debug("Return message of static_random_fill: %s", r_msg)
out = int(r_msg.split()[4])
logging.debug("Performance: %dMB * 1000 / %dms = %dMB/s", ksm_size, out,
(ksm_size * 1000 / out))
logging.debug(virt_test_utils.get_memory_info(lvms))
logging.debug("Phase 2: PASS")
def split_guest():
"""
Sequential split of pages on guests up to memory limit
"""
logging.info("Phase 3a: Sequential split of pages on guests up to "
"memory limit")
last_vm = 0
session = None
vm = None
for i in range(1, vmsc):
# Check VMs
for j in range(0, vmsc):
if not lvms[j].is_alive:
e_msg = ("VM %d died while executing static_random_fill in "
"VM %d on allocator loop" % (j, i))
raise error.TestFail(e_msg)
vm = lvms[i]
session = lsessions[i]
a_cmd = "mem.static_random_fill()"
logging.debug("Executing %s on ksm_overcommit_guest.py loop, "
"vm: %s", a_cmd, vm.name)
session.sendline(a_cmd)
out = ""
try:
logging.debug("Watching host memory while filling vm %s memory",
vm.name)
while not out.startswith("PASS") and not out.startswith("FAIL"):
if not vm.is_alive():
e_msg = ("VM %d died while executing static_random_fill"
" on allocator loop" % i)
raise error.TestFail(e_msg)
free_mem = int(utils.read_from_meminfo("MemFree"))
if (ksm_swap):
free_mem = (free_mem +
int(utils.read_from_meminfo("SwapFree")))
logging.debug("Free memory on host: %d", free_mem)
# We need to keep some memory for python to run.
if (free_mem < 64000) or (ksm_swap and
free_mem < (450000 * perf_ratio)):
vm.monitor.cmd("stop")
for j in range(0, i):
lvms[j].destroy(gracefully = False)
time.sleep(20)
vm.monitor.cmd("c")
logging.debug("Only %s free memory, killing %d guests",
free_mem, (i - 1))
last_vm = i
break
out = session.read_nonblocking(0.1)
time.sleep(2)
except OSError:
logging.debug("Only %s host free memory, killing %d guests",
free_mem, (i - 1))
logging.debug("Stopping %s", vm.name)
vm.monitor.cmd("stop")
for j in range(0, i):
logging.debug("Destroying %s", lvms[j].name)
lvms[j].destroy(gracefully = False)
time.sleep(20)
vm.monitor.cmd("c")
last_vm = i
if last_vm != 0:
break
logging.debug("Memory filled for guest %s", vm.name)
logging.info("Phase 3a: PASS")
logging.info("Phase 3b: Check if memory in max loading guest is right")
for i in range(last_vm + 1, vmsc):
lsessions[i].close()
if i == (vmsc - 1):
logging.debug(virt_test_utils.get_memory_info([lvms[i]]))
logging.debug("Destroying guest %s", lvms[i].name)
lvms[i].destroy(gracefully = False)
# Verify last machine with randomly generated memory
a_cmd = "mem.static_random_verify()"
_execute_allocator(a_cmd, lvms[last_vm], lsessions[last_vm],
(mem / 200 * 50 * perf_ratio))
logging.debug(virt_test_utils.get_memory_info([lvms[last_vm]]))
lsessions[i].cmd_output("die()", 20)
lvms[last_vm].destroy(gracefully = False)
logging.info("Phase 3b: PASS")
def split_parallel():
"""
Parallel page spliting
"""
logging.info("Phase 1: parallel page spliting")
# We have to wait until allocator is finished (it waits 5 seconds to
# clean the socket
session = lsessions[0]
vm = lvms[0]
for i in range(1, max_alloc):
lsessions.append(vm.wait_for_login(timeout=360))
session.cmd("swapoff -a", timeout=300)
for i in range(0, max_alloc):
# Start the allocator
_start_allocator(vm, lsessions[i], 60 * perf_ratio)
logging.info("Phase 1: PASS")
logging.info("Phase 2a: Simultaneous merging")
logging.debug("Memory used by allocator on guests = %dMB",
(ksm_size / max_alloc))
for i in range(0, max_alloc):
a_cmd = "mem = MemFill(%d, %s, %s)" % ((ksm_size / max_alloc),
skeys[i], dkeys[i])
_execute_allocator(a_cmd, vm, lsessions[i], 60 * perf_ratio)
a_cmd = "mem.value_fill(%d)" % (skeys[0])
_execute_allocator(a_cmd, vm, lsessions[i], 90 * perf_ratio)
# Wait until ksm_overcommit_guest.py merges the pages (3 * ksm_size / 3)
shm = 0
i = 0
logging.debug("Target shared memory size: %s", ksm_size)
while (shm < ksm_size):
if i > 64:
logging.debug(virt_test_utils.get_memory_info(lvms))
raise error.TestError("SHM didn't merge the memory until DL")
wt = ksm_size / 200 * perf_ratio
logging.debug("Waiting %ds before proceed...", wt)
time.sleep(wt)
if (new_ksm):
shm = get_ksmstat()
else:
shm = vm.get_shared_meminfo()
logging.debug("Shared meminfo after attempt %s: %s", i, shm)
i += 1
logging.debug(virt_test_utils.get_memory_info([vm]))
logging.info("Phase 2a: PASS")
logging.info("Phase 2b: Simultaneous spliting")
# Actual splitting
for i in range(0, max_alloc):
a_cmd = "mem.static_random_fill()"
data = _execute_allocator(a_cmd, vm, lsessions[i],
90 * perf_ratio)[1]
data = data.splitlines()[-1]
logging.debug(data)
out = int(data.split()[4])
logging.debug("Performance: %dMB * 1000 / %dms = %dMB/s",
(ksm_size / max_alloc), out,
(ksm_size * 1000 / out / max_alloc))
logging.debug(virt_test_utils.get_memory_info([vm]))
logging.info("Phase 2b: PASS")
logging.info("Phase 2c: Simultaneous verification")
for i in range(0, max_alloc):
a_cmd = "mem.static_random_verify()"
data = _execute_allocator(a_cmd, vm, lsessions[i],
(mem / 200 * 50 * perf_ratio))[1]
logging.info("Phase 2c: PASS")
logging.info("Phase 2d: Simultaneous merging")
# Actual splitting
for i in range(0, max_alloc):
a_cmd = "mem.value_fill(%d)" % skeys[0]
data = _execute_allocator(a_cmd, vm, lsessions[i],
120 * perf_ratio)[1]
logging.debug(virt_test_utils.get_memory_info([vm]))
logging.info("Phase 2d: PASS")
logging.info("Phase 2e: Simultaneous verification")
for i in range(0, max_alloc):
a_cmd = "mem.value_check(%d)" % skeys[0]
data = _execute_allocator(a_cmd, vm, lsessions[i],
(mem / 200 * 50 * perf_ratio))[1]
logging.info("Phase 2e: PASS")
logging.info("Phase 2f: Simultaneous spliting last 96B")
for i in range(0, max_alloc):
a_cmd = "mem.static_random_fill(96)"
data = _execute_allocator(a_cmd, vm, lsessions[i],
60 * perf_ratio)[1]
data = data.splitlines()[-1]
out = int(data.split()[4])
logging.debug("Performance: %dMB * 1000 / %dms = %dMB/s",
ksm_size/max_alloc, out,
(ksm_size * 1000 / out / max_alloc))
logging.debug(virt_test_utils.get_memory_info([vm]))
logging.info("Phase 2f: PASS")
logging.info("Phase 2g: Simultaneous verification last 96B")
for i in range(0, max_alloc):
a_cmd = "mem.static_random_verify(96)"
(match, data) = _execute_allocator(a_cmd, vm, lsessions[i],
(mem / 200 * 50 * perf_ratio))
logging.debug(virt_test_utils.get_memory_info([vm]))
logging.info("Phase 2g: PASS")
logging.debug("Cleaning up...")
for i in range(0, max_alloc):
lsessions[i].cmd_output("die()", 20)
session.close()
vm.destroy(gracefully = False)
# Main test code
logging.info("Starting phase 0: Initialization")
new_ksm = False
if (os.path.exists("/sys/kernel/mm/ksm/run")):
utils.run("echo 50 > /sys/kernel/mm/ksm/sleep_millisecs")
utils.run("echo 5000 > /sys/kernel/mm/ksm/pages_to_scan")
utils.run("echo 1 > /sys/kernel/mm/ksm/run")
e_up = "/sys/kernel/mm/transparent_hugepage/enabled"
e_rh = "/sys/kernel/mm/redhat_transparent_hugepage/enabled"
if os.path.exists(e_up):
utils.run("echo 'never' > %s" % e_up)
if os.path.exists(e_rh):
utils.run("echo 'never' > %s" % e_rh)
new_ksm = True
else:
try:
utils.run("modprobe ksm")
utils.run("ksmctl start 5000 100")
except error.CmdError, e:
raise error.TestFail("Failed to load KSM: %s" % e)
# host_reserve: mem reserve kept for the host system to run
host_reserve = int(params.get("ksm_host_reserve", -1))
if (host_reserve == -1):
# default host_reserve = MemAvailable + one_minimal_guest(128MB)
# later we add 64MB per additional guest
host_reserve = ((utils.memtotal() - utils.read_from_meminfo("MemFree"))
/ 1024 + 128)
# using default reserve
_host_reserve = True
else:
_host_reserve = False
# guest_reserve: mem reserve kept to avoid guest OS to kill processes
guest_reserve = int(params.get("ksm_guest_reserve", -1))
if (guest_reserve == -1):
# default guest_reserve = minimal_system_mem(256MB)
# later we add tmpfs overhead
guest_reserve = 256
# using default reserve
_guest_reserve = True
else:
_guest_reserve = False
max_vms = int(params.get("max_vms", 2))
overcommit = float(params.get("ksm_overcommit_ratio", 2.0))
max_alloc = int(params.get("ksm_parallel_ratio", 1))
# vmsc: count of all used VMs
vmsc = int(overcommit) + 1
vmsc = max(vmsc, max_vms)
if (params['ksm_mode'] == "serial"):
max_alloc = vmsc
if _host_reserve:
# First round of additional guest reserves
host_reserve += vmsc * 64
_host_reserve = vmsc
host_mem = (int(utils.memtotal()) / 1024 - host_reserve)
ksm_swap = False
if params.get("ksm_swap") == "yes":
ksm_swap = True
# Performance ratio
perf_ratio = params.get("ksm_perf_ratio")
if perf_ratio:
perf_ratio = float(perf_ratio)
else:
perf_ratio = 1
if (params['ksm_mode'] == "parallel"):
vmsc = 1
overcommit = 1
mem = host_mem
# 32bit system adjustment
if not params['image_name'].endswith("64"):
logging.debug("Probably i386 guest architecture, "
"max allocator mem = 2G")
# Guest can have more than 2G but
# kvm mem + 1MB (allocator itself) can't
if (host_mem > 3100):
mem = 3100
if os.popen("uname -i").readline().startswith("i386"):
logging.debug("Host is i386 architecture, max guest mem is 2G")
# Guest system with qemu overhead (64M) can't have more than 2G
if mem > 3100 - 64:
mem = 3100 - 64
else:
# mem: Memory of the guest systems. Maximum must be less than
# host's physical ram
mem = int(overcommit * host_mem / vmsc)
# 32bit system adjustment
if not params['image_name'].endswith("64"):
logging.debug("Probably i386 guest architecture, "
"max allocator mem = 2G")
# Guest can have more than 2G but
# kvm mem + 1MB (allocator itself) can't
if mem - guest_reserve - 1 > 3100:
vmsc = int(math.ceil((host_mem * overcommit) /
(3100 + guest_reserve)))
if _host_reserve:
host_reserve += (vmsc - _host_reserve) * 64
host_mem -= (vmsc - _host_reserve) * 64
_host_reserve = vmsc
mem = int(math.floor(host_mem * overcommit / vmsc))
if os.popen("uname -i").readline().startswith("i386"):
logging.debug("Host is i386 architecture, max guest mem is 2G")
# Guest system with qemu overhead (64M) can't have more than 2G
if mem > 3100 - 64:
vmsc = int(math.ceil((host_mem * overcommit) /
(3100 - 64.0)))
if _host_reserve:
host_reserve += (vmsc - _host_reserve) * 64
host_mem -= (vmsc - _host_reserve) * 64
_host_reserve = vmsc
mem = int(math.floor(host_mem * overcommit / vmsc))
# 0.055 represents OS + TMPFS additional reserve per guest ram MB
if _guest_reserve:
guest_reserve += math.ceil(mem * 0.055)
swap = int(utils.read_from_meminfo("SwapTotal")) / 1024
logging.debug("Overcommit = %f", overcommit)
logging.debug("True overcommit = %f ", (float(vmsc * mem) /
float(host_mem)))
logging.debug("Host memory = %dM", host_mem)
logging.debug("Guest memory = %dM", mem)
logging.debug("Using swap = %s", ksm_swap)
logging.debug("Swap = %dM", swap)
logging.debug("max_vms = %d", max_vms)
logging.debug("Count of all used VMs = %d", vmsc)
logging.debug("Performance_ratio = %f", perf_ratio)
# Generate unique keys for random series
skeys = []
dkeys = []
for i in range(0, max(vmsc, max_alloc)):
key = random.randrange(0, 255)
while key in skeys:
key = random.randrange(0, 255)
skeys.append(key)
key = random.randrange(0, 999)
while key in dkeys:
key = random.randrange(0, 999)
dkeys.append(key)
logging.debug("skeys: %s", skeys)
logging.debug("dkeys: %s", dkeys)
lvms = []
lsessions = []
# As we don't know the number and memory amount of VMs in advance,
# we need to specify and create them here
vm_name = params.get("main_vm")
params['mem'] = mem
params['vms'] = vm_name
# Associate pidfile name
params['pid_' + vm_name] = virt_utils.generate_tmp_file_name(vm_name,
'pid')
if not params.get('extra_params'):
params['extra_params'] = ' '
params['extra_params_' + vm_name] = params.get('extra_params')
params['extra_params_' + vm_name] += (" -pidfile %s" %
(params.get('pid_' + vm_name)))
params['extra_params'] = params.get('extra_params_'+vm_name)
# ksm_size: amount of memory used by allocator
ksm_size = mem - guest_reserve
logging.debug("Memory used by allocator on guests = %dM", ksm_size)
# Creating the first guest
virt_env_process.preprocess_vm(test, params, env, vm_name)
lvms.append(env.get_vm(vm_name))
if not lvms[0]:
raise error.TestError("VM object not found in environment")
if not lvms[0].is_alive():
raise error.TestError("VM seems to be dead; Test requires a living "
"VM")
logging.debug("Booting first guest %s", lvms[0].name)
lsessions.append(lvms[0].wait_for_login(timeout=360))
# Associate vm PID
try:
tmp = open(params.get('pid_' + vm_name), 'r')
params['pid_' + vm_name] = int(tmp.readline())
except:
raise error.TestFail("Could not get PID of %s" % (vm_name))
# Creating other guest systems
for i in range(1, vmsc):
vm_name = "vm" + str(i + 1)
params['pid_' + vm_name] = virt_utils.generate_tmp_file_name(vm_name,
'pid')
params['extra_params_' + vm_name] = params.get('extra_params')
params['extra_params_' + vm_name] += (" -pidfile %s" %
(params.get('pid_' + vm_name)))
params['extra_params'] = params.get('extra_params_' + vm_name)
# Last VM is later used to run more allocators simultaneously
lvms.append(lvms[0].clone(vm_name, params))
env.register_vm(vm_name, lvms[i])
params['vms'] += " " + vm_name
logging.debug("Booting guest %s", lvms[i].name)
lvms[i].create()
if not lvms[i].is_alive():
raise error.TestError("VM %s seems to be dead; Test requires a"
"living VM" % lvms[i].name)
lsessions.append(lvms[i].wait_for_login(timeout=360))
try:
tmp = open(params.get('pid_' + vm_name), 'r')
params['pid_' + vm_name] = int(tmp.readline())
except:
raise error.TestFail("Could not get PID of %s" % (vm_name))
# Let guests rest a little bit :-)
st = vmsc * 2 * perf_ratio
logging.debug("Waiting %ds before proceed", st)
time.sleep(vmsc * 2 * perf_ratio)
logging.debug(virt_test_utils.get_memory_info(lvms))
# Copy ksm_overcommit_guest.py into guests
pwd = os.path.join(os.environ['AUTODIR'],'tests/kvm')
vksmd_src = os.path.join(pwd, "scripts/ksm_overcommit_guest.py")
dst_dir = "/tmp"
for vm in lvms:
vm.copy_files_to(vksmd_src, dst_dir)
logging.info("Phase 0: PASS")
if params['ksm_mode'] == "parallel":
logging.info("Starting KSM test parallel mode")
split_parallel()
logging.info("KSM test parallel mode: PASS")
elif params['ksm_mode'] == "serial":
logging.info("Starting KSM test serial mode")
initialize_guests()
separate_first_guest()
split_guest()
logging.info("KSM test serial mode: PASS")