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