1import os, logging
2import time
3from tempfile import NamedTemporaryFile
4
5from autotest_lib.client.bin import test, utils
6from autotest_lib.client.common_lib import error
7from cgroup_common import Cgroup as CG
8from cgroup_common import CgroupModules
9
10class 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