普通文本  |  422行  |  15.41 KB

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()