blob: 959ce223255ad1a9432c93d5d697340986eeb80e [file] [log] [blame]
import os, logging
import time
from tempfile import NamedTemporaryFile
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from cgroup_common import Cgroup as CG
from cgroup_common import CgroupModules
class cgroup(test.test):
"""
Tests the cgroup functionalities. It works by creating a process (which is
also a python application) that will try to use CPU and memory. We will
then verify whether the cgroups rules are obeyed.
"""
version = 1
_client = ""
modules = CgroupModules()
def run_once(self):
"""
Try to access different resources which are restricted by cgroup.
"""
logging.info('Starting cgroup testing')
err = ""
# Run available tests
for i in ['memory', 'cpuset']:
logging.info("---< 'test_%s' START >---", i)
try:
if not self.modules.get_pwd(i):
raise error.TestFail("module not available/mounted")
t_function = getattr(self, "test_%s" % i)
t_function()
logging.info("---< 'test_%s' PASSED >---", i)
except AttributeError:
err += "%s, " % i
logging.error("test_%s: Test doesn't exist", i)
logging.info("---< 'test_%s' FAILED >---", i)
except Exception, inst:
err += "%s, " % i
logging.error("test_%s: %s", i, inst)
logging.info("---< 'test_%s' FAILED >---", i)
if err:
logging.error('Some subtests failed (%s)' % err[:-2])
raise error.TestFail('Some subtests failed (%s)' % err[:-2])
def setup(self):
"""
Setup
"""
logging.debug('Setting up cgroups modules')
self._client = os.path.join(self.bindir, "cgroup_client.py")
_modules = ['cpuset', 'ns', 'cpu', 'cpuacct', 'memory', 'devices',
'freezer', 'net_cls', 'blkio']
if (self.modules.init(_modules) <= 0):
raise error.TestFail('Can\'t mount any cgroup modules')
def cleanup(self):
"""
Unmount all cgroups and remove directories
"""
logging.info('Cleanup')
self.modules.cleanup()
#############################
# TESTS
#############################
def test_memory(self):
"""
Memory test
"""
def cleanup(supress=False):
# cleanup
logging.debug("test_memory: Cleanup")
err = ""
if item.rm_cgroup(pwd):
err += "\nCan't remove cgroup directory"
utils.system("swapon -a")
if err:
if supress:
logging.warning("Some parts of cleanup failed%s" % err)
else:
raise error.TestFail("Some parts of cleanup failed%s" % err)
# Preparation
item = CG('memory', self._client)
if item.initialize(self.modules):
raise error.TestFail("cgroup init failed")
if item.smoke_test():
raise error.TestFail("smoke_test failed")
pwd = item.mk_cgroup()
if pwd == None:
raise error.TestFail("Can't create cgroup")
logging.debug("test_memory: Memory filling test")
f = open('/proc/meminfo','r')
mem = f.readline()
while not mem.startswith("MemFree"):
mem = f.readline()
# Use only 1G or max of the free memory
mem = min(int(mem.split()[1])/1024, 1024)
mem = max(mem, 100) # at least 100M
memsw_limit_bytes = item.get_property("memory.memsw.limit_in_bytes",
supress=True)
if memsw_limit_bytes is not None:
memsw = True
# Clear swap
utils.system("swapoff -a")
utils.system("swapon -a")
f.seek(0)
swap = f.readline()
while not swap.startswith("SwapTotal"):
swap = f.readline()
swap = int(swap.split()[1])/1024
if swap < mem / 2:
logging.error("Not enough swap memory to test 'memsw'")
memsw = False
else:
# Doesn't support swap + memory limitation, disable swap
logging.info("System does not support 'memsw'")
utils.system("swapoff -a")
memsw = False
outf = NamedTemporaryFile('w+', prefix="cgroup_client-",
dir="/tmp")
logging.debug("test_memory: Initializition passed")
################################################
# Fill the memory without cgroup limitation
# Should pass
################################################
logging.debug("test_memory: Memfill WO cgroup")
ps = item.test("memfill %d %s" % (mem, outf.name))
ps.stdin.write('\n')
i = 0
while ps.poll() == None:
if i > 60:
break
i += 1
time.sleep(1)
if i > 60:
ps.terminate()
raise error.TestFail("Memory filling failed (WO cgroup)")
outf.seek(0)
outf.flush()
out = outf.readlines()
if (len(out) < 2) or (ps.poll() != 0):
raise error.TestFail("Process failed (WO cgroup); output:\n%s"
"\nReturn: %d" % (out, ps.poll()))
if not out[-1].startswith("PASS"):
raise error.TestFail("Unsuccessful memory filling "
"(WO cgroup)")
logging.debug("test_memory: Memfill WO cgroup passed")
################################################
# Fill the memory with 1/2 memory limit
# memsw: should swap out part of the process and pass
# WO memsw: should fail (SIGKILL)
################################################
logging.debug("test_memory: Memfill mem only limit")
ps = item.test("memfill %d %s" % (mem, outf.name))
if item.set_cgroup(ps.pid, pwd):
raise error.TestFail("Could not set cgroup")
if item.set_prop("memory.limit_in_bytes", ("%dM" % (mem/2)), pwd):
raise error.TestFail("Could not set mem limit (mem)")
ps.stdin.write('\n')
i = 0
while ps.poll() == None:
if i > 120:
break
i += 1
time.sleep(1)
if i > 120:
ps.terminate()
raise error.TestFail("Memory filling failed (mem)")
outf.seek(0)
outf.flush()
out = outf.readlines()
if (len(out) < 2):
raise error.TestFail("Process failed (mem); output:\n%s"
"\nReturn: %d" % (out, ps.poll()))
if memsw:
if not out[-1].startswith("PASS"):
logging.error("test_memory: cgroup_client.py returned %d; "
"output:\n%s", ps.poll(), out)
raise error.TestFail("Unsuccessful memory filling (mem)")
else:
if out[-1].startswith("PASS"):
raise error.TestFail("Unexpected memory filling (mem)")
else:
filled = int(out[-2].split()[1][:-1])
if mem/2 > 1.5 * filled:
logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
"python overhead upto 1/3 (mem))", mem/2,
filled)
else:
logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
"python overhead upto 1/3 (mem))", mem/2,
filled)
logging.debug("test_memory: Memfill mem only cgroup passed")
################################################
# Fill the memory with 1/2 memory+swap limit
# Should fail
# (memory.limit_in_bytes have to be set prior to this test)
################################################
if memsw:
logging.debug("test_memory: Memfill mem + swap limit")
ps = item.test("memfill %d %s" % (mem, outf.name))
if item.set_cgroup(ps.pid, pwd):
raise error.TestFail("Could not set cgroup (memsw)")
if item.set_prop("memory.memsw.limit_in_bytes", "%dM"%(mem/2), pwd):
raise error.TestFail("Could not set mem limit (memsw)")
ps.stdin.write('\n')
i = 0
while ps.poll() == None:
if i > 120:
break
i += 1
time.sleep(1)
if i > 120:
ps.terminate()
raise error.TestFail("Memory filling failed (mem)")
outf.seek(0)
outf.flush()
out = outf.readlines()
if (len(out) < 2):
raise error.TestFail("Process failed (memsw); output:\n%s"
"\nReturn: %d" % (out, ps.poll()))
if out[-1].startswith("PASS"):
raise error.TestFail("Unexpected memory filling (memsw)",
mem)
else:
filled = int(out[-2].split()[1][:-1])
if mem / 2 > 1.5 * filled:
logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
"python overhead upto 1/3 (memsw))", mem/2,
filled)
else:
logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
"python overhead upto 1/3 (memsw))", mem/2,
filled)
logging.debug("test_memory: Memfill mem + swap cgroup passed")
################################################
# CLEANUP
################################################
cleanup()
def test_cpuset(self):
"""
Cpuset test
1) Initiate CPU load on CPU0, than spread into CPU* - CPU0
"""
class per_cpu_load:
"""
Handles the per_cpu_load stats
self.values [cpus, cpu0, cpu1, ...]
"""
def __init__(self):
"""
Init
"""
self.values = []
self.f = open('/proc/stat', 'r')
line = self.f.readline()
while line:
if line.startswith('cpu'):
self.values.append(int(line.split()[1]))
else:
break
line = self.f.readline()
def reload(self):
"""
Reload current values
"""
self.values = self.get()
def get(self):
"""
Get the current values
@return vals: array of current values [cpus, cpu0, cpu1..]
"""
self.f.seek(0)
self.f.flush()
vals = []
for i in range(len(self.values)):
vals.append(int(self.f.readline().split()[1]))
return vals
def tick(self):
"""
Reload values and returns the load between the last tick/reload
@return vals: array of load between ticks/reloads
values [cpus, cpu0, cpu1..]
"""
vals = self.get()
ret = []
for i in range(len(self.values)):
ret.append(vals[i] - self.values[i])
self.values = vals
return ret
def cleanup(supress=False):
# cleanup
logging.debug("test_cpuset: Cleanup")
err = ""
try:
for task in tasks:
for i in range(10):
task.terminate()
if task.poll() != None:
break
time.sleep(1)
if i >= 9:
logging.error("test_cpuset: Subprocess didn't finish")
except Exception, inst:
err += "\nCan't terminate tasks: %s" % inst
if item.rm_cgroup(pwd):
err += "\nCan't remove cgroup direcotry"
if err:
if supress:
logging.warning("Some parts of cleanup failed%s" % err)
else:
raise error.TestFail("Some parts of cleanup failed%s" % err)
# Preparation
item = CG('cpuset', self._client)
if item.initialize(self.modules):
raise error.TestFail("cgroup init failed")
# FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
# thus smoke_test won't work
#if item.smoke_test():
# raise error.TestFail("smoke_test failed")
try:
# Available cpus: cpuset.cpus = "0-$CPUS\n"
no_cpus = int(item.get_prop("cpuset.cpus").split('-')[1]) + 1
except:
raise error.TestFail("Failed to get no_cpus or no_cpus = 1")
pwd = item.mk_cgroup()
if pwd == None:
raise error.TestFail("Can't create cgroup")
# FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
try:
tmp = item.get_prop("cpuset.cpus")
item.set_property("cpuset.cpus", tmp, pwd)
tmp = item.get_prop("cpuset.mems")
item.set_property("cpuset.mems", tmp, pwd)
except:
cleanup(True)
raise error.TestFail("Failed to set cpus and mems of"
"a new cgroup")
################################################
# Cpu allocation test
# Use cpu0 and verify, than all cpu* - cpu0 and verify
################################################
logging.debug("test_cpuset: Cpu allocation test")
tasks = []
# Run no_cpus + 1 jobs
for i in range(no_cpus + 1):
tasks.append(item.test("cpu"))
if item.set_cgroup(tasks[i].pid, pwd):
cleanup(True)
raise error.TestFail("Failed to set cgroup")
tasks[i].stdin.write('\n')
stats = per_cpu_load()
# Use only the first CPU
item.set_property("cpuset.cpus", 0, pwd)
stats.reload()
time.sleep(10)
# [0] = all cpus
s1 = stats.tick()[1:]
s2 = s1[1:]
s1 = s1[0]
for _s in s2:
if s1 < _s:
cleanup(True)
raise error.TestFail("Unused processor had higher utilization\n"
"used cpu: %s, remaining cpus: %s"
% (s1, s2))
if no_cpus == 2:
item.set_property("cpuset.cpus", "1", pwd)
else:
item.set_property("cpuset.cpus", "1-%d"%(no_cpus-1), pwd)
stats.reload()
time.sleep(10)
s1 = stats.tick()[1:]
s2 = s1[0]
s1 = s1[1:]
for _s in s1:
if s2 > _s:
cleanup(True)
raise error.TestFail("Unused processor had higher utilization\n"
"used cpus: %s, remaining cpu: %s"
% (s1, s2))
logging.debug("test_cpuset: Cpu allocation test passed")
################################################
# CLEANUP
################################################
cleanup()