Home | History | Annotate | Download | only in cgroup
      1 import os, logging
      2 import time
      3 from tempfile import NamedTemporaryFile
      4 
      5 from autotest_lib.client.bin import test, utils
      6 from autotest_lib.client.common_lib import error
      7 from cgroup_common import Cgroup as CG
      8 from cgroup_common import CgroupModules
      9 
     10 class cgroup(test.test):
     11     """
     12     Tests the cgroup functionalities. It works by creating a process (which is
     13     also a python application) that will try to use CPU and memory. We will
     14     then verify whether the cgroups rules are obeyed.
     15     """
     16     version = 1
     17     _client = ""
     18     modules = CgroupModules()
     19 
     20     def run_once(self):
     21         """
     22             Try to access different resources which are restricted by cgroup.
     23         """
     24         logging.info('Starting cgroup testing')
     25 
     26         err = ""
     27         # Run available tests
     28         for i in ['memory', 'cpuset']:
     29             logging.info("---< 'test_%s' START >---", i)
     30             try:
     31                 if not self.modules.get_pwd(i):
     32                     raise error.TestFail("module not available/mounted")
     33                 t_function = getattr(self, "test_%s" % i)
     34                 t_function()
     35                 logging.info("---< 'test_%s' PASSED >---", i)
     36             except AttributeError:
     37                 err += "%s, " % i
     38                 logging.error("test_%s: Test doesn't exist", i)
     39                 logging.info("---< 'test_%s' FAILED >---", i)
     40             except Exception, inst:
     41                 err += "%s, " % i
     42                 logging.error("test_%s: %s", i, inst)
     43                 logging.info("---< 'test_%s' FAILED >---", i)
     44 
     45         if err:
     46             logging.error('Some subtests failed (%s)' % err[:-2])
     47             raise error.TestFail('Some subtests failed (%s)' % err[:-2])
     48 
     49 
     50     def setup(self):
     51         """
     52         Setup
     53         """
     54         logging.debug('Setting up cgroups modules')
     55 
     56         self._client = os.path.join(self.bindir, "cgroup_client.py")
     57 
     58         _modules = ['cpuset', 'ns', 'cpu', 'cpuacct', 'memory', 'devices',
     59                     'freezer', 'net_cls', 'blkio']
     60         if (self.modules.init(_modules) <= 0):
     61             raise error.TestFail('Can\'t mount any cgroup modules')
     62 
     63 
     64     def cleanup(self):
     65         """
     66         Unmount all cgroups and remove directories
     67         """
     68         logging.info('Cleanup')
     69         self.modules.cleanup()
     70 
     71 
     72     #############################
     73     # TESTS
     74     #############################
     75     def test_memory(self):
     76         """
     77         Memory test
     78         """
     79         def cleanup(supress=False):
     80             # cleanup
     81             logging.debug("test_memory: Cleanup")
     82             err = ""
     83             if item.rm_cgroup(pwd):
     84                 err += "\nCan't remove cgroup directory"
     85 
     86             utils.system("swapon -a")
     87 
     88             if err:
     89                 if supress:
     90                     logging.warning("Some parts of cleanup failed%s" % err)
     91                 else:
     92                     raise error.TestFail("Some parts of cleanup failed%s" % err)
     93 
     94         # Preparation
     95         item = CG('memory', self._client)
     96         if item.initialize(self.modules):
     97             raise error.TestFail("cgroup init failed")
     98 
     99         if item.smoke_test():
    100             raise error.TestFail("smoke_test failed")
    101 
    102         pwd = item.mk_cgroup()
    103         if pwd == None:
    104             raise error.TestFail("Can't create cgroup")
    105 
    106         logging.debug("test_memory: Memory filling test")
    107 
    108         f = open('/proc/meminfo','r')
    109         mem = f.readline()
    110         while not mem.startswith("MemFree"):
    111             mem = f.readline()
    112         # Use only 1G or max of the free memory
    113         mem = min(int(mem.split()[1])/1024, 1024)
    114         mem = max(mem, 100) # at least 100M
    115         memsw_limit_bytes = item.get_property("memory.memsw.limit_in_bytes",
    116                                               supress=True)
    117         if memsw_limit_bytes is not None:
    118             memsw = True
    119             # Clear swap
    120             utils.system("swapoff -a")
    121             utils.system("swapon -a")
    122             f.seek(0)
    123             swap = f.readline()
    124             while not swap.startswith("SwapTotal"):
    125                 swap = f.readline()
    126             swap = int(swap.split()[1])/1024
    127             if swap < mem / 2:
    128                 logging.error("Not enough swap memory to test 'memsw'")
    129                 memsw = False
    130         else:
    131             # Doesn't support swap + memory limitation, disable swap
    132             logging.info("System does not support 'memsw'")
    133             utils.system("swapoff -a")
    134             memsw = False
    135         outf = NamedTemporaryFile('w+', prefix="cgroup_client-",
    136                                   dir="/tmp")
    137         logging.debug("test_memory: Initializition passed")
    138 
    139         ################################################
    140         # Fill the memory without cgroup limitation
    141         # Should pass
    142         ################################################
    143         logging.debug("test_memory: Memfill WO cgroup")
    144         ps = item.test("memfill %d %s" % (mem, outf.name))
    145         ps.stdin.write('\n')
    146         i = 0
    147         while ps.poll() == None:
    148             if i > 60:
    149                 break
    150             i += 1
    151             time.sleep(1)
    152         if i > 60:
    153             ps.terminate()
    154             raise error.TestFail("Memory filling failed (WO cgroup)")
    155         outf.seek(0)
    156         outf.flush()
    157         out = outf.readlines()
    158         if (len(out) < 2) or (ps.poll() != 0):
    159             raise error.TestFail("Process failed (WO cgroup); output:\n%s"
    160                                  "\nReturn: %d" % (out, ps.poll()))
    161         if not out[-1].startswith("PASS"):
    162             raise error.TestFail("Unsuccessful memory filling "
    163                                  "(WO cgroup)")
    164         logging.debug("test_memory: Memfill WO cgroup passed")
    165 
    166         ################################################
    167         # Fill the memory with 1/2 memory limit
    168         # memsw: should swap out part of the process and pass
    169         # WO memsw: should fail (SIGKILL)
    170         ################################################
    171         logging.debug("test_memory: Memfill mem only limit")
    172         ps = item.test("memfill %d %s" % (mem, outf.name))
    173         if item.set_cgroup(ps.pid, pwd):
    174             raise error.TestFail("Could not set cgroup")
    175         if item.set_prop("memory.limit_in_bytes", ("%dM" % (mem/2)), pwd):
    176             raise error.TestFail("Could not set mem limit (mem)")
    177         ps.stdin.write('\n')
    178         i = 0
    179         while ps.poll() == None:
    180             if i > 120:
    181                 break
    182             i += 1
    183             time.sleep(1)
    184         if i > 120:
    185             ps.terminate()
    186             raise error.TestFail("Memory filling failed (mem)")
    187         outf.seek(0)
    188         outf.flush()
    189         out = outf.readlines()
    190         if (len(out) < 2):
    191             raise error.TestFail("Process failed (mem); output:\n%s"
    192                           "\nReturn: %d" % (out, ps.poll()))
    193         if memsw:
    194             if not out[-1].startswith("PASS"):
    195                 logging.error("test_memory: cgroup_client.py returned %d; "
    196                               "output:\n%s", ps.poll(), out)
    197                 raise error.TestFail("Unsuccessful memory filling (mem)")
    198         else:
    199             if out[-1].startswith("PASS"):
    200                 raise error.TestFail("Unexpected memory filling (mem)")
    201             else:
    202                 filled = int(out[-2].split()[1][:-1])
    203                 if mem/2 > 1.5 * filled:
    204                     logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
    205                                   "python overhead upto 1/3 (mem))", mem/2,
    206                                   filled)
    207                 else:
    208                     logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
    209                                   "python overhead upto 1/3 (mem))", mem/2,
    210                                   filled)
    211         logging.debug("test_memory: Memfill mem only cgroup passed")
    212 
    213         ################################################
    214         # Fill the memory with 1/2 memory+swap limit
    215         # Should fail
    216         # (memory.limit_in_bytes have to be set prior to this test)
    217         ################################################
    218         if memsw:
    219             logging.debug("test_memory: Memfill mem + swap limit")
    220             ps = item.test("memfill %d %s" % (mem, outf.name))
    221             if item.set_cgroup(ps.pid, pwd):
    222                 raise error.TestFail("Could not set cgroup (memsw)")
    223             if item.set_prop("memory.memsw.limit_in_bytes", "%dM"%(mem/2), pwd):
    224                 raise error.TestFail("Could not set mem limit (memsw)")
    225             ps.stdin.write('\n')
    226             i = 0
    227             while ps.poll() == None:
    228                 if i > 120:
    229                     break
    230                 i += 1
    231                 time.sleep(1)
    232             if i > 120:
    233                 ps.terminate()
    234                 raise error.TestFail("Memory filling failed (mem)")
    235             outf.seek(0)
    236             outf.flush()
    237             out = outf.readlines()
    238             if (len(out) < 2):
    239                 raise error.TestFail("Process failed (memsw); output:\n%s"
    240                                      "\nReturn: %d" % (out, ps.poll()))
    241             if out[-1].startswith("PASS"):
    242                 raise error.TestFail("Unexpected memory filling (memsw)",
    243                               mem)
    244             else:
    245                 filled = int(out[-2].split()[1][:-1])
    246                 if mem / 2 > 1.5 * filled:
    247                     logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
    248                                   "python overhead upto 1/3 (memsw))", mem/2,
    249                                   filled)
    250                 else:
    251                     logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
    252                                   "python overhead upto 1/3 (memsw))", mem/2,
    253                                   filled)
    254             logging.debug("test_memory: Memfill mem + swap cgroup passed")
    255 
    256         ################################################
    257         # CLEANUP
    258         ################################################
    259         cleanup()
    260 
    261 
    262 
    263     def test_cpuset(self):
    264         """
    265         Cpuset test
    266         1) Initiate CPU load on CPU0, than spread into CPU* - CPU0
    267         """
    268         class per_cpu_load:
    269             """
    270             Handles the per_cpu_load stats
    271             self.values [cpus, cpu0, cpu1, ...]
    272             """
    273             def __init__(self):
    274                 """
    275                 Init
    276                 """
    277                 self.values = []
    278                 self.f = open('/proc/stat', 'r')
    279                 line = self.f.readline()
    280                 while line:
    281                     if line.startswith('cpu'):
    282                         self.values.append(int(line.split()[1]))
    283                     else:
    284                         break
    285                     line = self.f.readline()
    286 
    287             def reload(self):
    288                 """
    289                 Reload current values
    290                 """
    291                 self.values = self.get()
    292 
    293             def get(self):
    294                 """
    295                 Get the current values
    296                 @return vals: array of current values [cpus, cpu0, cpu1..]
    297                 """
    298                 self.f.seek(0)
    299                 self.f.flush()
    300                 vals = []
    301                 for i in range(len(self.values)):
    302                     vals.append(int(self.f.readline().split()[1]))
    303                 return vals
    304 
    305             def tick(self):
    306                 """
    307                 Reload values and returns the load between the last tick/reload
    308                 @return vals: array of load between ticks/reloads
    309                               values [cpus, cpu0, cpu1..]
    310                 """
    311                 vals = self.get()
    312                 ret = []
    313                 for i in range(len(self.values)):
    314                     ret.append(vals[i] - self.values[i])
    315                 self.values = vals
    316                 return ret
    317 
    318         def cleanup(supress=False):
    319             # cleanup
    320             logging.debug("test_cpuset: Cleanup")
    321             err = ""
    322             try:
    323                 for task in tasks:
    324                     for i in range(10):
    325                         task.terminate()
    326                         if task.poll() != None:
    327                             break
    328                         time.sleep(1)
    329                     if i >= 9:
    330                         logging.error("test_cpuset: Subprocess didn't finish")
    331             except Exception, inst:
    332                 err += "\nCan't terminate tasks: %s" % inst
    333             if item.rm_cgroup(pwd):
    334                 err += "\nCan't remove cgroup direcotry"
    335             if err:
    336                 if supress:
    337                     logging.warning("Some parts of cleanup failed%s" % err)
    338                 else:
    339                     raise error.TestFail("Some parts of cleanup failed%s" % err)
    340 
    341         # Preparation
    342         item = CG('cpuset', self._client)
    343         if item.initialize(self.modules):
    344             raise error.TestFail("cgroup init failed")
    345 
    346         # FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
    347         # thus smoke_test won't work
    348         #if item.smoke_test():
    349         #    raise error.TestFail("smoke_test failed")
    350 
    351         try:
    352             # Available cpus: cpuset.cpus = "0-$CPUS\n"
    353             no_cpus = int(item.get_prop("cpuset.cpus").split('-')[1]) + 1
    354         except:
    355             raise error.TestFail("Failed to get no_cpus or no_cpus = 1")
    356 
    357         pwd = item.mk_cgroup()
    358         if pwd == None:
    359             raise error.TestFail("Can't create cgroup")
    360         # FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
    361         try:
    362             tmp = item.get_prop("cpuset.cpus")
    363             item.set_property("cpuset.cpus", tmp, pwd)
    364             tmp = item.get_prop("cpuset.mems")
    365             item.set_property("cpuset.mems", tmp, pwd)
    366         except:
    367             cleanup(True)
    368             raise error.TestFail("Failed to set cpus and mems of"
    369                                  "a new cgroup")
    370 
    371         ################################################
    372         # Cpu allocation test
    373         # Use cpu0 and verify, than all cpu* - cpu0 and verify
    374         ################################################
    375         logging.debug("test_cpuset: Cpu allocation test")
    376 
    377         tasks = []
    378         # Run no_cpus + 1 jobs
    379         for i in range(no_cpus + 1):
    380             tasks.append(item.test("cpu"))
    381             if item.set_cgroup(tasks[i].pid, pwd):
    382                 cleanup(True)
    383                 raise error.TestFail("Failed to set cgroup")
    384             tasks[i].stdin.write('\n')
    385         stats = per_cpu_load()
    386         # Use only the first CPU
    387         item.set_property("cpuset.cpus", 0, pwd)
    388         stats.reload()
    389         time.sleep(10)
    390         # [0] = all cpus
    391         s1 = stats.tick()[1:]
    392         s2 = s1[1:]
    393         s1 = s1[0]
    394         for _s in s2:
    395             if s1 < _s:
    396                 cleanup(True)
    397                 raise error.TestFail("Unused processor had higher utilization\n"
    398                                      "used cpu: %s, remaining cpus: %s"
    399                                      % (s1, s2))
    400 
    401         if no_cpus == 2:
    402             item.set_property("cpuset.cpus", "1", pwd)
    403         else:
    404             item.set_property("cpuset.cpus", "1-%d"%(no_cpus-1), pwd)
    405         stats.reload()
    406         time.sleep(10)
    407         s1 = stats.tick()[1:]
    408         s2 = s1[0]
    409         s1 = s1[1:]
    410         for _s in s1:
    411             if s2 > _s:
    412                 cleanup(True)
    413                 raise error.TestFail("Unused processor had higher utilization\n"
    414                                      "used cpus: %s, remaining cpu: %s"
    415                                      % (s1, s2))
    416         logging.debug("test_cpuset: Cpu allocation test passed")
    417 
    418         ################################################
    419         # CLEANUP
    420         ################################################
    421         cleanup()
    422