attr.py 15 KB

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