| 1 | |
| 2 |
|
| 3 | import configparser
|
| 4 | import os
|
| 5 | import sys
|
| 6 | import glob
|
| 7 | import optparse
|
| 8 | import platform
|
| 9 | import tempfile
|
| 10 | import logging
|
| 11 | import re
|
| 12 | import shutil
|
| 13 | import subprocess
|
| 14 |
|
| 15 | def data_equal(a, b):
|
| 16 | |
| 17 | a_list = a.split('|')
|
| 18 | b_list = b.split('|')
|
| 19 |
|
| 20 | for a_item in a_list:
|
| 21 | for b_item in b_list:
|
| 22 | if (a_item == b_item):
|
| 23 | return True
|
| 24 | elif (a_item == '*') or (b_item == '*'):
|
| 25 | return True
|
| 26 |
|
| 27 | return False
|
| 28 |
|
| 29 | class Fail(Exception):
|
| 30 | def __init__(self, test, msg):
|
| 31 | self.msg = msg
|
| 32 | self.test = test
|
| 33 | def getMsg(self):
|
| 34 | return '\'%s\' - %s' % (self.test.path, self.msg)
|
| 35 |
|
| 36 | class Notest(Exception):
|
| 37 | def __init__(self, test, arch):
|
| 38 | self.arch = arch
|
| 39 | self.test = test
|
| 40 | def getMsg(self):
|
| 41 | return '[%s] \'%s\'' % (self.arch, self.test.path)
|
| 42 |
|
| 43 | class Unsup(Exception):
|
| 44 | def __init__(self, test):
|
| 45 | self.test = test
|
| 46 | def getMsg(self):
|
| 47 | return '\'%s\'' % self.test.path
|
| 48 |
|
| 49 | class Event(dict):
|
| 50 | terms = [
|
| 51 | 'cpu',
|
| 52 | 'flags',
|
| 53 | 'type',
|
| 54 | 'size',
|
| 55 | 'config',
|
| 56 | 'sample_period',
|
| 57 | 'sample_type',
|
| 58 | 'read_format',
|
| 59 | 'disabled',
|
| 60 | 'inherit',
|
| 61 | 'pinned',
|
| 62 | 'exclusive',
|
| 63 | 'exclude_user',
|
| 64 | 'exclude_kernel',
|
| 65 | 'exclude_hv',
|
| 66 | 'exclude_idle',
|
| 67 | 'mmap',
|
| 68 | 'comm',
|
| 69 | 'freq',
|
| 70 | 'inherit_stat',
|
| 71 | 'enable_on_exec',
|
| 72 | 'task',
|
| 73 | 'watermark',
|
| 74 | 'precise_ip',
|
| 75 | 'mmap_data',
|
| 76 | 'sample_id_all',
|
| 77 | 'exclude_host',
|
| 78 | 'exclude_guest',
|
| 79 | 'exclude_callchain_kernel',
|
| 80 | 'exclude_callchain_user',
|
| 81 | 'wakeup_events',
|
| 82 | 'bp_type',
|
| 83 | 'config1',
|
| 84 | 'config2',
|
| 85 | 'branch_sample_type',
|
| 86 | 'sample_regs_user',
|
| 87 | 'sample_stack_user',
|
| 88 | ]
|
| 89 |
|
| 90 | def add(self, data):
|
| 91 | for key, val in data:
|
| 92 | log.debug(" %s = %s" % (key, val))
|
| 93 | self[key] = val
|
| 94 |
|
| 95 | def __init__(self, name, data, base):
|
| 96 | log.debug(" Event %s" % name);
|
| 97 | self.name = name;
|
| 98 | self.group = ''
|
| 99 | self.add(base)
|
| 100 | self.add(data)
|
| 101 |
|
| 102 | def equal(self, other):
|
| 103 | for t in Event.terms:
|
| 104 | log.debug(" [%s] %s %s" % (t, self[t], other[t]));
|
| 105 | if t not in self or t not in other:
|
| 106 | return False
|
| 107 | if not data_equal(self[t], other[t]):
|
| 108 | return False
|
| 109 | return True
|
| 110 |
|
| 111 | def optional(self):
|
| 112 | if 'optional' in self and self['optional'] == '1':
|
| 113 | return True
|
| 114 | return False
|
| 115 |
|
| 116 | def diff(self, other):
|
| 117 | for t in Event.terms:
|
| 118 | if t not in self or t not in other:
|
| 119 | continue
|
| 120 | if not data_equal(self[t], other[t]):
|
| 121 | log.warning("expected %s=%s, got %s" % (t, self[t], other[t]))
|
| 122 |
|
| 123 | def parse_version(version):
|
| 124 | if not version:
|
| 125 | return None
|
| 126 | return [int(v) for v in version.split(".")[0:2]]
|
| 127 |
|
| 128 | |
| 129 | |
| 130 | |
| 131 | |
| 132 | |
| 133 | |
| 134 | |
| 135 | |
| 136 | |
| 137 | |
| 138 | |
| 139 | |
| 140 | |
| 141 | |
| 142 | |
| 143 | |
| 144 | |
| 145 | |
| 146 | |
| 147 | class Test(object):
|
| 148 | def __init__(self, path, options):
|
| 149 | parser = configparser.ConfigParser()
|
| 150 | parser.read(path)
|
| 151 |
|
| 152 | log.warning("running '%s'" % path)
|
| 153 |
|
| 154 | self.path = path
|
| 155 | self.test_dir = options.test_dir
|
| 156 | self.perf = options.perf
|
| 157 | self.command = parser.get('config', 'command')
|
| 158 | self.args = parser.get('config', 'args')
|
| 159 |
|
| 160 | try:
|
| 161 | self.ret = parser.get('config', 'ret')
|
| 162 | except:
|
| 163 | self.ret = 0
|
| 164 |
|
| 165 | self.test_ret = parser.getboolean('config', 'test_ret', fallback=False)
|
| 166 |
|
| 167 | try:
|
| 168 | self.arch = parser.get('config', 'arch')
|
| 169 | log.warning("test limitation '%s'" % self.arch)
|
| 170 | except:
|
| 171 | self.arch = ''
|
| 172 |
|
| 173 | self.auxv = parser.get('config', 'auxv', fallback=None)
|
| 174 | self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None))
|
| 175 | self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None))
|
| 176 | self.expect = {}
|
| 177 | self.result = {}
|
| 178 | log.debug(" loading expected events");
|
| 179 | self.load_events(path, self.expect)
|
| 180 |
|
| 181 | def is_event(self, name):
|
| 182 | if name.find("event") == -1:
|
| 183 | return False
|
| 184 | else:
|
| 185 | return True
|
| 186 |
|
| 187 | def skip_test_kernel_since(self):
|
| 188 | if not self.kernel_since:
|
| 189 | return False
|
| 190 | return not self.kernel_since <= parse_version(platform.release())
|
| 191 |
|
| 192 | def skip_test_kernel_until(self):
|
| 193 | if not self.kernel_until:
|
| 194 | return False
|
| 195 | return not parse_version(platform.release()) < self.kernel_until
|
| 196 |
|
| 197 | def skip_test_auxv(self):
|
| 198 | def new_auxv(a, pattern):
|
| 199 | items = list(filter(None, pattern.split(a)))
|
| 200 | |
| 201 | if items[0] == "AT_HWCAP":
|
| 202 | value = int(items[-1], 16)
|
| 203 | else:
|
| 204 | try:
|
| 205 | value = int(items[-1], 0)
|
| 206 | except:
|
| 207 | value = items[-1]
|
| 208 | return (items[0], value)
|
| 209 |
|
| 210 | if not self.auxv:
|
| 211 | return False
|
| 212 | auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \
|
| 213 | .decode(sys.stdout.encoding)
|
| 214 | pattern = re.compile(r"[: ]+")
|
| 215 | auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()])
|
| 216 | return not eval(self.auxv)
|
| 217 |
|
| 218 | def skip_test_arch(self, myarch):
|
| 219 | |
| 220 | if self.arch == '':
|
| 221 | |
| 222 | return False
|
| 223 |
|
| 224 | |
| 225 | arch_list = self.arch.split(',')
|
| 226 |
|
| 227 | |
| 228 | if arch_list[0][0] == '!':
|
| 229 | arch_list[0] = arch_list[0][1:]
|
| 230 | log.warning("excluded architecture list %s" % arch_list)
|
| 231 | for arch_item in arch_list:
|
| 232 | |
| 233 | if arch_item == myarch:
|
| 234 | return True
|
| 235 | return False
|
| 236 |
|
| 237 | for arch_item in arch_list:
|
| 238 | |
| 239 | if arch_item == myarch:
|
| 240 | return False
|
| 241 | return True
|
| 242 |
|
| 243 | def restore_sample_rate(self, value=10000):
|
| 244 | try:
|
| 245 | |
| 246 | with open("/proc/sys/kernel/perf_event_max_sample_rate", "r") as fIn:
|
| 247 | curr_value = fIn.readline()
|
| 248 | |
| 249 | if not curr_value or int(curr_value) < int(value):
|
| 250 | with open("/proc/sys/kernel/perf_event_max_sample_rate", "w") as fOut:
|
| 251 | fOut.write(str(value))
|
| 252 |
|
| 253 | except IOError as e:
|
| 254 | log.warning("couldn't restore sample_rate value: I/O error %s" % e)
|
| 255 | except ValueError as e:
|
| 256 | log.warning("couldn't restore sample_rate value: Value error %s" % e)
|
| 257 | except TypeError as e:
|
| 258 | log.warning("couldn't restore sample_rate value: Type error %s" % e)
|
| 259 |
|
| 260 | def load_events(self, path, events):
|
| 261 | parser_event = configparser.ConfigParser()
|
| 262 | parser_event.read(path)
|
| 263 |
|
| 264 | |
| 265 | |
| 266 | |
| 267 | for section in filter(self.is_event, parser_event.sections()):
|
| 268 |
|
| 269 | parser_items = parser_event.items(section);
|
| 270 | base_items = {}
|
| 271 |
|
| 272 | |
| 273 | if (':' in section):
|
| 274 | base = section[section.index(':') + 1:]
|
| 275 | parser_base = configparser.ConfigParser()
|
| 276 | parser_base.read(self.test_dir + '/' + base)
|
| 277 | base_items = parser_base.items('event')
|
| 278 |
|
| 279 | e = Event(section, parser_items, base_items)
|
| 280 | events[section] = e
|
| 281 |
|
| 282 | def run_cmd(self, tempdir):
|
| 283 | junk1, junk2, junk3, junk4, myarch = (os.uname())
|
| 284 |
|
| 285 | if self.skip_test_arch(myarch):
|
| 286 | raise Notest(self, myarch)
|
| 287 |
|
| 288 | if self.skip_test_auxv():
|
| 289 | raise Notest(self, "auxv skip")
|
| 290 |
|
| 291 | if self.skip_test_kernel_since():
|
| 292 | raise Notest(self, "old kernel skip")
|
| 293 |
|
| 294 | if self.skip_test_kernel_until():
|
| 295 | raise Notest(self, "new kernel skip")
|
| 296 |
|
| 297 | self.restore_sample_rate()
|
| 298 | cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
|
| 299 | self.perf, self.command, tempdir, self.args)
|
| 300 | ret = os.WEXITSTATUS(os.system(cmd))
|
| 301 |
|
| 302 | log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret)))
|
| 303 |
|
| 304 | if not data_equal(str(ret), str(self.ret)):
|
| 305 | if self.test_ret:
|
| 306 | raise Fail(self, "Perf exit code failure")
|
| 307 | else:
|
| 308 | raise Unsup(self)
|
| 309 |
|
| 310 | def compare(self, expect, result):
|
| 311 | match = {}
|
| 312 |
|
| 313 | log.debug(" compare");
|
| 314 |
|
| 315 | |
| 316 | |
| 317 | for exp_name, exp_event in expect.items():
|
| 318 | exp_list = []
|
| 319 | res_event = {}
|
| 320 | log.debug(" matching [%s]" % exp_name)
|
| 321 | for res_name, res_event in result.items():
|
| 322 | log.debug(" to [%s]" % res_name)
|
| 323 | if (exp_event.equal(res_event)):
|
| 324 | exp_list.append(res_name)
|
| 325 | log.debug(" ->OK")
|
| 326 | else:
|
| 327 | log.debug(" ->FAIL");
|
| 328 |
|
| 329 | log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list)))
|
| 330 |
|
| 331 | |
| 332 | if not exp_list:
|
| 333 | if exp_event.optional():
|
| 334 | log.debug(" %s does not match, but is optional" % exp_name)
|
| 335 | else:
|
| 336 | if not res_event:
|
| 337 | log.debug(" res_event is empty");
|
| 338 | else:
|
| 339 | exp_event.diff(res_event)
|
| 340 | raise Fail(self, 'match failure');
|
| 341 |
|
| 342 | match[exp_name] = exp_list
|
| 343 |
|
| 344 | |
| 345 | |
| 346 | for exp_name, exp_event in expect.items():
|
| 347 | group = exp_event.group
|
| 348 |
|
| 349 | if (group == ''):
|
| 350 | continue
|
| 351 |
|
| 352 | for res_name in match[exp_name]:
|
| 353 | res_group = result[res_name].group
|
| 354 | if res_group not in match[group]:
|
| 355 | raise Fail(self, 'group failure')
|
| 356 |
|
| 357 | log.debug(" group: [%s] matches group leader %s" %
|
| 358 | (exp_name, str(match[group])))
|
| 359 |
|
| 360 | log.debug(" matched")
|
| 361 |
|
| 362 | def resolve_groups(self, events):
|
| 363 | for name, event in events.items():
|
| 364 | group_fd = event['group_fd'];
|
| 365 | if group_fd == '-1':
|
| 366 | continue;
|
| 367 |
|
| 368 | for iname, ievent in events.items():
|
| 369 | if (ievent['fd'] == group_fd):
|
| 370 | event.group = iname
|
| 371 | log.debug('[%s] has group leader [%s]' % (name, iname))
|
| 372 | break;
|
| 373 |
|
| 374 | def run(self):
|
| 375 | tempdir = tempfile.mkdtemp();
|
| 376 |
|
| 377 | try:
|
| 378 | |
| 379 | self.run_cmd(tempdir);
|
| 380 |
|
| 381 | |
| 382 | log.debug(" loading result events");
|
| 383 | for f in glob.glob(tempdir + '/event*'):
|
| 384 | self.load_events(f, self.result);
|
| 385 |
|
| 386 | |
| 387 | self.resolve_groups(self.expect);
|
| 388 | self.resolve_groups(self.result);
|
| 389 |
|
| 390 | |
| 391 | self.compare(self.expect, self.result)
|
| 392 | self.compare(self.result, self.expect)
|
| 393 |
|
| 394 | finally:
|
| 395 | |
| 396 | shutil.rmtree(tempdir)
|
| 397 |
|
| 398 |
|
| 399 | def run_tests(options):
|
| 400 | for f in glob.glob(options.test_dir + '/' + options.test):
|
| 401 | try:
|
| 402 | Test(f, options).run()
|
| 403 | except Unsup as obj:
|
| 404 | log.warning("unsupp %s" % obj.getMsg())
|
| 405 | except Notest as obj:
|
| 406 | log.warning("skipped %s" % obj.getMsg())
|
| 407 |
|
| 408 | def setup_log(verbose):
|
| 409 | global log
|
| 410 | level = logging.CRITICAL
|
| 411 |
|
| 412 | if verbose == 1:
|
| 413 | level = logging.WARNING
|
| 414 | if verbose == 2:
|
| 415 | level = logging.INFO
|
| 416 | if verbose >= 3:
|
| 417 | level = logging.DEBUG
|
| 418 |
|
| 419 | log = logging.getLogger('test')
|
| 420 | log.setLevel(level)
|
| 421 | ch = logging.StreamHandler()
|
| 422 | ch.setLevel(level)
|
| 423 | formatter = logging.Formatter('%(message)s')
|
| 424 | ch.setFormatter(formatter)
|
| 425 | log.addHandler(ch)
|
| 426 |
|
| 427 | USAGE = '''%s [OPTIONS]
|
| 428 | -d dir # tests dir
|
| 429 | -p path # perf binary
|
| 430 | -t test # single test
|
| 431 | -v # verbose level
|
| 432 | ''' % sys.argv[0]
|
| 433 |
|
| 434 | def main():
|
| 435 | parser = optparse.OptionParser(usage=USAGE)
|
| 436 |
|
| 437 | parser.add_option("-t", "--test",
|
| 438 | action="store", type="string", dest="test")
|
| 439 | parser.add_option("-d", "--test-dir",
|
| 440 | action="store", type="string", dest="test_dir")
|
| 441 | parser.add_option("-p", "--perf",
|
| 442 | action="store", type="string", dest="perf")
|
| 443 | parser.add_option("-v", "--verbose",
|
| 444 | default=0, action="count", dest="verbose")
|
| 445 |
|
| 446 | options, args = parser.parse_args()
|
| 447 | if args:
|
| 448 | parser.error('FAILED wrong arguments %s' % ' '.join(args))
|
| 449 | return -1
|
| 450 |
|
| 451 | setup_log(options.verbose)
|
| 452 |
|
| 453 | if not options.test_dir:
|
| 454 | print('FAILED no -d option specified')
|
| 455 | sys.exit(-1)
|
| 456 |
|
| 457 | if not options.test:
|
| 458 | options.test = 'test*'
|
| 459 |
|
| 460 | try:
|
| 461 | run_tests(options)
|
| 462 |
|
| 463 | except Fail as obj:
|
| 464 | print("FAILED %s" % obj.getMsg())
|
| 465 | sys.exit(-1)
|
| 466 |
|
| 467 | sys.exit(0)
|
| 468 |
|
| 469 | if __name__ == '__main__':
|
| 470 | main()
|