attr.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. # SPDX-License-Identifier: GPL-2.0
  2. from __future__ import print_function
  3. import os
  4. import sys
  5. import glob
  6. import optparse
  7. import platform
  8. import tempfile
  9. import logging
  10. import re
  11. import shutil
  12. import subprocess
  13. try:
  14. import configparser
  15. except ImportError:
  16. import ConfigParser as configparser
  17. def data_equal(a, b):
  18. # Allow multiple values in assignment separated by '|'
  19. a_list = a.split('|')
  20. b_list = b.split('|')
  21. for a_item in a_list:
  22. for b_item in b_list:
  23. if (a_item == b_item):
  24. return True
  25. elif (a_item == '*') or (b_item == '*'):
  26. return True
  27. return False
  28. class Fail(Exception):
  29. def __init__(self, test, msg):
  30. self.msg = msg
  31. self.test = test
  32. def getMsg(self):
  33. return '\'%s\' - %s' % (self.test.path, self.msg)
  34. class Notest(Exception):
  35. def __init__(self, test, arch):
  36. self.arch = arch
  37. self.test = test
  38. def getMsg(self):
  39. return '[%s] \'%s\'' % (self.arch, self.test.path)
  40. class Unsup(Exception):
  41. def __init__(self, test):
  42. self.test = test
  43. def getMsg(self):
  44. return '\'%s\'' % self.test.path
  45. class Event(dict):
  46. terms = [
  47. 'cpu',
  48. 'flags',
  49. 'type',
  50. 'size',
  51. 'config',
  52. 'sample_period',
  53. 'sample_type',
  54. 'read_format',
  55. 'disabled',
  56. 'inherit',
  57. 'pinned',
  58. 'exclusive',
  59. 'exclude_user',
  60. 'exclude_kernel',
  61. 'exclude_hv',
  62. 'exclude_idle',
  63. 'mmap',
  64. 'comm',
  65. 'freq',
  66. 'inherit_stat',
  67. 'enable_on_exec',
  68. 'task',
  69. 'watermark',
  70. 'precise_ip',
  71. 'mmap_data',
  72. 'sample_id_all',
  73. 'exclude_host',
  74. 'exclude_guest',
  75. 'exclude_callchain_kernel',
  76. 'exclude_callchain_user',
  77. 'wakeup_events',
  78. 'bp_type',
  79. 'config1',
  80. 'config2',
  81. 'branch_sample_type',
  82. 'sample_regs_user',
  83. 'sample_stack_user',
  84. ]
  85. def add(self, data):
  86. for key, val in data:
  87. log.debug(" %s = %s" % (key, val))
  88. self[key] = val
  89. def __init__(self, name, data, base):
  90. log.debug(" Event %s" % name);
  91. self.name = name;
  92. self.group = ''
  93. self.add(base)
  94. self.add(data)
  95. def equal(self, other):
  96. for t in Event.terms:
  97. log.debug(" [%s] %s %s" % (t, self[t], other[t]));
  98. if t not in self or t not in other:
  99. return False
  100. if not data_equal(self[t], other[t]):
  101. return False
  102. return True
  103. def optional(self):
  104. if 'optional' in self and self['optional'] == '1':
  105. return True
  106. return False
  107. def diff(self, other):
  108. for t in Event.terms:
  109. if t not in self or t not in other:
  110. continue
  111. if not data_equal(self[t], other[t]):
  112. log.warning("expected %s=%s, got %s" % (t, self[t], other[t]))
  113. def parse_version(version):
  114. if not version:
  115. return None
  116. return [int(v) for v in version.split(".")[0:2]]
  117. # Test file description needs to have following sections:
  118. # [config]
  119. # - just single instance in file
  120. # - needs to specify:
  121. # 'command' - perf command name
  122. # 'args' - special command arguments
  123. # 'ret' - Skip test if Perf doesn't exit with this value (0 by default)
  124. # 'test_ret'- If set to 'true', fail test instead of skipping for 'ret' argument
  125. # 'arch' - architecture specific test (optional)
  126. # comma separated list, ! at the beginning
  127. # negates it.
  128. # 'auxv' - Truthy statement that is evaled in the scope of the auxv map. When false,
  129. # the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional)
  130. # 'kernel_since' - Inclusive kernel version from which the test will start running. Only the
  131. # first two values are supported, for example "6.1" (optional)
  132. # 'kernel_until' - Exclusive kernel version from which the test will stop running. (optional)
  133. # [eventX:base]
  134. # - one or multiple instances in file
  135. # - expected values assignments
  136. class Test(object):
  137. def __init__(self, path, options):
  138. parser = configparser.ConfigParser()
  139. parser.read(path)
  140. log.warning("running '%s'" % path)
  141. self.path = path
  142. self.test_dir = options.test_dir
  143. self.perf = options.perf
  144. self.command = parser.get('config', 'command')
  145. self.args = parser.get('config', 'args')
  146. try:
  147. self.ret = parser.get('config', 'ret')
  148. except:
  149. self.ret = 0
  150. self.test_ret = parser.getboolean('config', 'test_ret', fallback=False)
  151. try:
  152. self.arch = parser.get('config', 'arch')
  153. log.warning("test limitation '%s'" % self.arch)
  154. except:
  155. self.arch = ''
  156. self.auxv = parser.get('config', 'auxv', fallback=None)
  157. self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None))
  158. self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None))
  159. self.expect = {}
  160. self.result = {}
  161. log.debug(" loading expected events");
  162. self.load_events(path, self.expect)
  163. def is_event(self, name):
  164. if name.find("event") == -1:
  165. return False
  166. else:
  167. return True
  168. def skip_test_kernel_since(self):
  169. if not self.kernel_since:
  170. return False
  171. return not self.kernel_since <= parse_version(platform.release())
  172. def skip_test_kernel_until(self):
  173. if not self.kernel_until:
  174. return False
  175. return not parse_version(platform.release()) < self.kernel_until
  176. def skip_test_auxv(self):
  177. def new_auxv(a, pattern):
  178. items = list(filter(None, pattern.split(a)))
  179. # AT_HWCAP is hex but doesn't have a prefix, so special case it
  180. if items[0] == "AT_HWCAP":
  181. value = int(items[-1], 16)
  182. else:
  183. try:
  184. value = int(items[-1], 0)
  185. except:
  186. value = items[-1]
  187. return (items[0], value)
  188. if not self.auxv:
  189. return False
  190. auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \
  191. .decode(sys.stdout.encoding)
  192. pattern = re.compile(r"[: ]+")
  193. auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()])
  194. return not eval(self.auxv)
  195. def skip_test_arch(self, myarch):
  196. # If architecture not set always run test
  197. if self.arch == '':
  198. # log.warning("test for arch %s is ok" % myarch)
  199. return False
  200. # Allow multiple values in assignment separated by ','
  201. arch_list = self.arch.split(',')
  202. # Handle negated list such as !s390x,ppc
  203. if arch_list[0][0] == '!':
  204. arch_list[0] = arch_list[0][1:]
  205. log.warning("excluded architecture list %s" % arch_list)
  206. for arch_item in arch_list:
  207. # log.warning("test for %s arch is %s" % (arch_item, myarch))
  208. if arch_item == myarch:
  209. return True
  210. return False
  211. for arch_item in arch_list:
  212. # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch))
  213. if arch_item == myarch:
  214. return False
  215. return True
  216. def load_events(self, path, events):
  217. parser_event = configparser.ConfigParser()
  218. parser_event.read(path)
  219. # The event record section header contains 'event' word,
  220. # optionaly followed by ':' allowing to load 'parent
  221. # event' first as a base
  222. for section in filter(self.is_event, parser_event.sections()):
  223. parser_items = parser_event.items(section);
  224. base_items = {}
  225. # Read parent event if there's any
  226. if (':' in section):
  227. base = section[section.index(':') + 1:]
  228. parser_base = configparser.ConfigParser()
  229. parser_base.read(self.test_dir + '/' + base)
  230. base_items = parser_base.items('event')
  231. e = Event(section, parser_items, base_items)
  232. events[section] = e
  233. def run_cmd(self, tempdir):
  234. junk1, junk2, junk3, junk4, myarch = (os.uname())
  235. if self.skip_test_arch(myarch):
  236. raise Notest(self, myarch)
  237. if self.skip_test_auxv():
  238. raise Notest(self, "auxv skip")
  239. if self.skip_test_kernel_since():
  240. raise Notest(self, "old kernel skip")
  241. if self.skip_test_kernel_until():
  242. raise Notest(self, "new kernel skip")
  243. cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
  244. self.perf, self.command, tempdir, self.args)
  245. ret = os.WEXITSTATUS(os.system(cmd))
  246. log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret)))
  247. if not data_equal(str(ret), str(self.ret)):
  248. if self.test_ret:
  249. raise Fail(self, "Perf exit code failure")
  250. else:
  251. raise Unsup(self)
  252. def compare(self, expect, result):
  253. match = {}
  254. log.debug(" compare");
  255. # For each expected event find all matching
  256. # events in result. Fail if there's not any.
  257. for exp_name, exp_event in expect.items():
  258. exp_list = []
  259. res_event = {}
  260. log.debug(" matching [%s]" % exp_name)
  261. for res_name, res_event in result.items():
  262. log.debug(" to [%s]" % res_name)
  263. if (exp_event.equal(res_event)):
  264. exp_list.append(res_name)
  265. log.debug(" ->OK")
  266. else:
  267. log.debug(" ->FAIL");
  268. log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list)))
  269. # we did not any matching event - fail
  270. if not exp_list:
  271. if exp_event.optional():
  272. log.debug(" %s does not match, but is optional" % exp_name)
  273. else:
  274. if not res_event:
  275. log.debug(" res_event is empty");
  276. else:
  277. exp_event.diff(res_event)
  278. raise Fail(self, 'match failure');
  279. match[exp_name] = exp_list
  280. # For each defined group in the expected events
  281. # check we match the same group in the result.
  282. for exp_name, exp_event in expect.items():
  283. group = exp_event.group
  284. if (group == ''):
  285. continue
  286. for res_name in match[exp_name]:
  287. res_group = result[res_name].group
  288. if res_group not in match[group]:
  289. raise Fail(self, 'group failure')
  290. log.debug(" group: [%s] matches group leader %s" %
  291. (exp_name, str(match[group])))
  292. log.debug(" matched")
  293. def resolve_groups(self, events):
  294. for name, event in events.items():
  295. group_fd = event['group_fd'];
  296. if group_fd == '-1':
  297. continue;
  298. for iname, ievent in events.items():
  299. if (ievent['fd'] == group_fd):
  300. event.group = iname
  301. log.debug('[%s] has group leader [%s]' % (name, iname))
  302. break;
  303. def run(self):
  304. tempdir = tempfile.mkdtemp();
  305. try:
  306. # run the test script
  307. self.run_cmd(tempdir);
  308. # load events expectation for the test
  309. log.debug(" loading result events");
  310. for f in glob.glob(tempdir + '/event*'):
  311. self.load_events(f, self.result);
  312. # resolve group_fd to event names
  313. self.resolve_groups(self.expect);
  314. self.resolve_groups(self.result);
  315. # do the expectation - results matching - both ways
  316. self.compare(self.expect, self.result)
  317. self.compare(self.result, self.expect)
  318. finally:
  319. # cleanup
  320. shutil.rmtree(tempdir)
  321. def run_tests(options):
  322. for f in glob.glob(options.test_dir + '/' + options.test):
  323. try:
  324. Test(f, options).run()
  325. except Unsup as obj:
  326. log.warning("unsupp %s" % obj.getMsg())
  327. except Notest as obj:
  328. log.warning("skipped %s" % obj.getMsg())
  329. def setup_log(verbose):
  330. global log
  331. level = logging.CRITICAL
  332. if verbose == 1:
  333. level = logging.WARNING
  334. if verbose == 2:
  335. level = logging.INFO
  336. if verbose >= 3:
  337. level = logging.DEBUG
  338. log = logging.getLogger('test')
  339. log.setLevel(level)
  340. ch = logging.StreamHandler()
  341. ch.setLevel(level)
  342. formatter = logging.Formatter('%(message)s')
  343. ch.setFormatter(formatter)
  344. log.addHandler(ch)
  345. USAGE = '''%s [OPTIONS]
  346. -d dir # tests dir
  347. -p path # perf binary
  348. -t test # single test
  349. -v # verbose level
  350. ''' % sys.argv[0]
  351. def main():
  352. parser = optparse.OptionParser(usage=USAGE)
  353. parser.add_option("-t", "--test",
  354. action="store", type="string", dest="test")
  355. parser.add_option("-d", "--test-dir",
  356. action="store", type="string", dest="test_dir")
  357. parser.add_option("-p", "--perf",
  358. action="store", type="string", dest="perf")
  359. parser.add_option("-v", "--verbose",
  360. default=0, action="count", dest="verbose")
  361. options, args = parser.parse_args()
  362. if args:
  363. parser.error('FAILED wrong arguments %s' % ' '.join(args))
  364. return -1
  365. setup_log(options.verbose)
  366. if not options.test_dir:
  367. print('FAILED no -d option specified')
  368. sys.exit(-1)
  369. if not options.test:
  370. options.test = 'test*'
  371. try:
  372. run_tests(options)
  373. except Fail as obj:
  374. print("FAILED %s" % obj.getMsg())
  375. sys.exit(-1)
  376. sys.exit(0)
  377. if __name__ == '__main__':
  378. main()