bench.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433
  1. #!/usr/bin/env python3
  2. #
  3. # Script to compile and runs benches.
  4. #
  5. # Example:
  6. # ./scripts/bench.py runners/bench_runner -b
  7. #
  8. # Copyright (c) 2022, The littlefs authors.
  9. # SPDX-License-Identifier: BSD-3-Clause
  10. #
  11. import collections as co
  12. import csv
  13. import errno
  14. import glob
  15. import itertools as it
  16. import math as m
  17. import os
  18. import pty
  19. import re
  20. import shlex
  21. import shutil
  22. import signal
  23. import subprocess as sp
  24. import threading as th
  25. import time
  26. import toml
  27. RUNNER_PATH = './runners/bench_runner'
  28. HEADER_PATH = 'runners/bench_runner.h'
  29. GDB_PATH = ['gdb']
  30. VALGRIND_PATH = ['valgrind']
  31. PERF_SCRIPT = ['./scripts/perf.py']
  32. def openio(path, mode='r', buffering=-1):
  33. # allow '-' for stdin/stdout
  34. if path == '-':
  35. if mode == 'r':
  36. return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
  37. else:
  38. return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
  39. else:
  40. return open(path, mode, buffering)
  41. class BenchCase:
  42. # create a BenchCase object from a config
  43. def __init__(self, config, args={}):
  44. self.name = config.pop('name')
  45. self.path = config.pop('path')
  46. self.suite = config.pop('suite')
  47. self.lineno = config.pop('lineno', None)
  48. self.if_ = config.pop('if', None)
  49. if isinstance(self.if_, bool):
  50. self.if_ = 'true' if self.if_ else 'false'
  51. self.code = config.pop('code')
  52. self.code_lineno = config.pop('code_lineno', None)
  53. self.in_ = config.pop('in',
  54. config.pop('suite_in', None))
  55. # figure out defines and build possible permutations
  56. self.defines = set()
  57. self.permutations = []
  58. # defines can be a dict or a list or dicts
  59. suite_defines = config.pop('suite_defines', {})
  60. if not isinstance(suite_defines, list):
  61. suite_defines = [suite_defines]
  62. defines = config.pop('defines', {})
  63. if not isinstance(defines, list):
  64. defines = [defines]
  65. def csplit(v):
  66. # split commas but only outside of parens
  67. parens = 0
  68. i_ = 0
  69. for i in range(len(v)):
  70. if v[i] == ',' and parens == 0:
  71. yield v[i_:i]
  72. i_ = i+1
  73. elif v[i] in '([{':
  74. parens += 1
  75. elif v[i] in '}])':
  76. parens -= 1
  77. if v[i_:].strip():
  78. yield v[i_:]
  79. def parse_define(v):
  80. # a define entry can be a list
  81. if isinstance(v, list):
  82. for v_ in v:
  83. yield from parse_define(v_)
  84. # or a string
  85. elif isinstance(v, str):
  86. # which can be comma-separated values, with optional
  87. # range statements. This matches the runtime define parser in
  88. # the runner itself.
  89. for v_ in csplit(v):
  90. m = re.search(r'\brange\b\s*\('
  91. '(?P<start>[^,\s]*)'
  92. '\s*(?:,\s*(?P<stop>[^,\s]*)'
  93. '\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
  94. v_)
  95. if m:
  96. start = (int(m.group('start'), 0)
  97. if m.group('start') else 0)
  98. stop = (int(m.group('stop'), 0)
  99. if m.group('stop') else None)
  100. step = (int(m.group('step'), 0)
  101. if m.group('step') else 1)
  102. if m.lastindex <= 1:
  103. start, stop = 0, start
  104. for x in range(start, stop, step):
  105. yield from parse_define('%s(%d)%s' % (
  106. v_[:m.start()], x, v_[m.end():]))
  107. else:
  108. yield v_
  109. # or a literal value
  110. elif isinstance(v, bool):
  111. yield 'true' if v else 'false'
  112. else:
  113. yield v
  114. # build possible permutations
  115. for suite_defines_ in suite_defines:
  116. self.defines |= suite_defines_.keys()
  117. for defines_ in defines:
  118. self.defines |= defines_.keys()
  119. self.permutations.extend(dict(perm) for perm in it.product(*(
  120. [(k, v) for v in parse_define(vs)]
  121. for k, vs in sorted((suite_defines_ | defines_).items()))))
  122. for k in config.keys():
  123. print('%swarning:%s in %s, found unused key %r' % (
  124. '\x1b[01;33m' if args['color'] else '',
  125. '\x1b[m' if args['color'] else '',
  126. self.name,
  127. k),
  128. file=sys.stderr)
  129. class BenchSuite:
  130. # create a BenchSuite object from a toml file
  131. def __init__(self, path, args={}):
  132. self.path = path
  133. self.name = os.path.basename(path)
  134. if self.name.endswith('.toml'):
  135. self.name = self.name[:-len('.toml')]
  136. # load toml file and parse bench cases
  137. with open(self.path) as f:
  138. # load benches
  139. config = toml.load(f)
  140. # find line numbers
  141. f.seek(0)
  142. case_linenos = []
  143. code_linenos = []
  144. for i, line in enumerate(f):
  145. match = re.match(
  146. '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
  147. '|' '(?P<code>code\s*=)',
  148. line)
  149. if match and match.group('case'):
  150. case_linenos.append((i+1, match.group('name')))
  151. elif match and match.group('code'):
  152. code_linenos.append(i+2)
  153. # sort in case toml parsing did not retain order
  154. case_linenos.sort()
  155. cases = config.pop('cases')
  156. for (lineno, name), (nlineno, _) in it.zip_longest(
  157. case_linenos, case_linenos[1:],
  158. fillvalue=(float('inf'), None)):
  159. code_lineno = min(
  160. (l for l in code_linenos if l >= lineno and l < nlineno),
  161. default=None)
  162. cases[name]['lineno'] = lineno
  163. cases[name]['code_lineno'] = code_lineno
  164. self.if_ = config.pop('if', None)
  165. if isinstance(self.if_, bool):
  166. self.if_ = 'true' if self.if_ else 'false'
  167. self.code = config.pop('code', None)
  168. self.code_lineno = min(
  169. (l for l in code_linenos
  170. if not case_linenos or l < case_linenos[0][0]),
  171. default=None)
  172. # a couple of these we just forward to all cases
  173. defines = config.pop('defines', {})
  174. in_ = config.pop('in', None)
  175. self.cases = []
  176. for name, case in sorted(cases.items(),
  177. key=lambda c: c[1].get('lineno')):
  178. self.cases.append(BenchCase(config={
  179. 'name': name,
  180. 'path': path + (':%d' % case['lineno']
  181. if 'lineno' in case else ''),
  182. 'suite': self.name,
  183. 'suite_defines': defines,
  184. 'suite_in': in_,
  185. **case},
  186. args=args))
  187. # combine per-case defines
  188. self.defines = set.union(*(
  189. set(case.defines) for case in self.cases))
  190. for k in config.keys():
  191. print('%swarning:%s in %s, found unused key %r' % (
  192. '\x1b[01;33m' if args['color'] else '',
  193. '\x1b[m' if args['color'] else '',
  194. self.name,
  195. k),
  196. file=sys.stderr)
  197. def compile(bench_paths, **args):
  198. # find .toml files
  199. paths = []
  200. for path in bench_paths:
  201. if os.path.isdir(path):
  202. path = path + '/*.toml'
  203. for path in glob.glob(path):
  204. paths.append(path)
  205. if not paths:
  206. print('no bench suites found in %r?' % bench_paths)
  207. sys.exit(-1)
  208. # load the suites
  209. suites = [BenchSuite(path, args) for path in paths]
  210. suites.sort(key=lambda s: s.name)
  211. # check for name conflicts, these will cause ambiguity problems later
  212. # when running benches
  213. seen = {}
  214. for suite in suites:
  215. if suite.name in seen:
  216. print('%swarning:%s conflicting suite %r, %s and %s' % (
  217. '\x1b[01;33m' if args['color'] else '',
  218. '\x1b[m' if args['color'] else '',
  219. suite.name,
  220. suite.path,
  221. seen[suite.name].path),
  222. file=sys.stderr)
  223. seen[suite.name] = suite
  224. for case in suite.cases:
  225. # only allow conflicts if a case and its suite share a name
  226. if case.name in seen and not (
  227. isinstance(seen[case.name], BenchSuite)
  228. and seen[case.name].cases == [case]):
  229. print('%swarning:%s conflicting case %r, %s and %s' % (
  230. '\x1b[01;33m' if args['color'] else '',
  231. '\x1b[m' if args['color'] else '',
  232. case.name,
  233. case.path,
  234. seen[case.name].path),
  235. file=sys.stderr)
  236. seen[case.name] = case
  237. # we can only compile one bench suite at a time
  238. if not args.get('source'):
  239. if len(suites) > 1:
  240. print('more than one bench suite for compilation? (%r)' % bench_paths)
  241. sys.exit(-1)
  242. suite = suites[0]
  243. # write generated bench source
  244. if 'output' in args:
  245. with openio(args['output'], 'w') as f:
  246. _write = f.write
  247. def write(s):
  248. f.lineno += s.count('\n')
  249. _write(s)
  250. def writeln(s=''):
  251. f.lineno += s.count('\n') + 1
  252. _write(s)
  253. _write('\n')
  254. f.lineno = 1
  255. f.write = write
  256. f.writeln = writeln
  257. f.writeln("// Generated by %s:" % sys.argv[0])
  258. f.writeln("//")
  259. f.writeln("// %s" % ' '.join(sys.argv))
  260. f.writeln("//")
  261. f.writeln()
  262. # include bench_runner.h in every generated file
  263. f.writeln("#include \"%s\"" % args['include'])
  264. f.writeln()
  265. # write out generated functions, this can end up in different
  266. # files depending on the "in" attribute
  267. #
  268. # note it's up to the specific generated file to declare
  269. # the bench defines
  270. def write_case_functions(f, suite, case):
  271. # create case define functions
  272. if case.defines:
  273. # deduplicate defines by value to try to reduce the
  274. # number of functions we generate
  275. define_cbs = {}
  276. for i, defines in enumerate(case.permutations):
  277. for k, v in sorted(defines.items()):
  278. if v not in define_cbs:
  279. name = ('__bench__%s__%s__%d'
  280. % (case.name, k, i))
  281. define_cbs[v] = name
  282. f.writeln('intmax_t %s('
  283. '__attribute__((unused)) '
  284. 'void *data) {' % name)
  285. f.writeln(4*' '+'return %s;' % v)
  286. f.writeln('}')
  287. f.writeln()
  288. f.writeln('const bench_define_t '
  289. '__bench__%s__defines[]['
  290. 'BENCH_IMPLICIT_DEFINE_COUNT+%d] = {'
  291. % (case.name, len(suite.defines)))
  292. for defines in case.permutations:
  293. f.writeln(4*' '+'{')
  294. for k, v in sorted(defines.items()):
  295. f.writeln(8*' '+'[%-24s] = {%s, NULL},' % (
  296. k+'_i', define_cbs[v]))
  297. f.writeln(4*' '+'},')
  298. f.writeln('};')
  299. f.writeln()
  300. # create case filter function
  301. if suite.if_ is not None or case.if_ is not None:
  302. f.writeln('bool __bench__%s__filter(void) {'
  303. % (case.name))
  304. f.writeln(4*' '+'return %s;'
  305. % ' && '.join('(%s)' % if_
  306. for if_ in [suite.if_, case.if_]
  307. if if_ is not None))
  308. f.writeln('}')
  309. f.writeln()
  310. # create case run function
  311. f.writeln('void __bench__%s__run('
  312. '__attribute__((unused)) struct lfs_config *cfg) {'
  313. % (case.name))
  314. f.writeln(4*' '+'// bench case %s' % case.name)
  315. if case.code_lineno is not None:
  316. f.writeln(4*' '+'#line %d "%s"'
  317. % (case.code_lineno, suite.path))
  318. f.write(case.code)
  319. if case.code_lineno is not None:
  320. f.writeln(4*' '+'#line %d "%s"'
  321. % (f.lineno+1, args['output']))
  322. f.writeln('}')
  323. f.writeln()
  324. if not args.get('source'):
  325. if suite.code is not None:
  326. if suite.code_lineno is not None:
  327. f.writeln('#line %d "%s"'
  328. % (suite.code_lineno, suite.path))
  329. f.write(suite.code)
  330. if suite.code_lineno is not None:
  331. f.writeln('#line %d "%s"'
  332. % (f.lineno+1, args['output']))
  333. f.writeln()
  334. if suite.defines:
  335. for i, define in enumerate(sorted(suite.defines)):
  336. f.writeln('#ifndef %s' % define)
  337. f.writeln('#define %-24s '
  338. 'BENCH_IMPLICIT_DEFINE_COUNT+%d' % (define+'_i', i))
  339. f.writeln('#define %-24s '
  340. 'BENCH_DEFINE(%s)' % (define, define+'_i'))
  341. f.writeln('#endif')
  342. f.writeln()
  343. # create case functions
  344. for case in suite.cases:
  345. if case.in_ is None:
  346. write_case_functions(f, suite, case)
  347. else:
  348. if case.defines:
  349. f.writeln('extern const bench_define_t '
  350. '__bench__%s__defines[]['
  351. 'BENCH_IMPLICIT_DEFINE_COUNT+%d];'
  352. % (case.name, len(suite.defines)))
  353. if suite.if_ is not None or case.if_ is not None:
  354. f.writeln('extern bool __bench__%s__filter('
  355. 'void);'
  356. % (case.name))
  357. f.writeln('extern void __bench__%s__run('
  358. 'struct lfs_config *cfg);'
  359. % (case.name))
  360. f.writeln()
  361. # create suite struct
  362. f.writeln('#if defined(__APPLE__)')
  363. f.writeln('__attribute__((section("__DATA,_bench_suites")))')
  364. f.writeln('#else')
  365. # note we place this in the custom bench_suites section with
  366. # minimum alignment, otherwise GCC ups the alignment to
  367. # 32-bytes for some reason
  368. f.writeln('__attribute__((section("_bench_suites"), '
  369. 'aligned(1)))')
  370. f.writeln('#endif')
  371. f.writeln('const struct bench_suite __bench__%s__suite = {'
  372. % suite.name)
  373. f.writeln(4*' '+'.name = "%s",' % suite.name)
  374. f.writeln(4*' '+'.path = "%s",' % suite.path)
  375. f.writeln(4*' '+'.flags = 0,')
  376. if suite.defines:
  377. # create suite define names
  378. f.writeln(4*' '+'.define_names = (const char *const['
  379. 'BENCH_IMPLICIT_DEFINE_COUNT+%d]){' % (
  380. len(suite.defines)))
  381. for k in sorted(suite.defines):
  382. f.writeln(8*' '+'[%-24s] = "%s",' % (k+'_i', k))
  383. f.writeln(4*' '+'},')
  384. f.writeln(4*' '+'.define_count = '
  385. 'BENCH_IMPLICIT_DEFINE_COUNT+%d,' % len(suite.defines))
  386. f.writeln(4*' '+'.cases = (const struct bench_case[]){')
  387. for case in suite.cases:
  388. # create case structs
  389. f.writeln(8*' '+'{')
  390. f.writeln(12*' '+'.name = "%s",' % case.name)
  391. f.writeln(12*' '+'.path = "%s",' % case.path)
  392. f.writeln(12*' '+'.flags = 0,')
  393. f.writeln(12*' '+'.permutations = %d,'
  394. % len(case.permutations))
  395. if case.defines:
  396. f.writeln(12*' '+'.defines '
  397. '= (const bench_define_t*)__bench__%s__defines,'
  398. % (case.name))
  399. if suite.if_ is not None or case.if_ is not None:
  400. f.writeln(12*' '+'.filter = __bench__%s__filter,'
  401. % (case.name))
  402. f.writeln(12*' '+'.run = __bench__%s__run,'
  403. % (case.name))
  404. f.writeln(8*' '+'},')
  405. f.writeln(4*' '+'},')
  406. f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
  407. f.writeln('};')
  408. f.writeln()
  409. else:
  410. # copy source
  411. f.writeln('#line 1 "%s"' % args['source'])
  412. with open(args['source']) as sf:
  413. shutil.copyfileobj(sf, f)
  414. f.writeln()
  415. # write any internal benches
  416. for suite in suites:
  417. for case in suite.cases:
  418. if (case.in_ is not None
  419. and os.path.normpath(case.in_)
  420. == os.path.normpath(args['source'])):
  421. # write defines, but note we need to undef any
  422. # new defines since we're in someone else's file
  423. if suite.defines:
  424. for i, define in enumerate(
  425. sorted(suite.defines)):
  426. f.writeln('#ifndef %s' % define)
  427. f.writeln('#define %-24s '
  428. 'BENCH_IMPLICIT_DEFINE_COUNT+%d' % (
  429. define+'_i', i))
  430. f.writeln('#define %-24s '
  431. 'BENCH_DEFINE(%s)' % (
  432. define, define+'_i'))
  433. f.writeln('#define '
  434. '__BENCH__%s__NEEDS_UNDEF' % (
  435. define))
  436. f.writeln('#endif')
  437. f.writeln()
  438. write_case_functions(f, suite, case)
  439. if suite.defines:
  440. for define in sorted(suite.defines):
  441. f.writeln('#ifdef __BENCH__%s__NEEDS_UNDEF'
  442. % define)
  443. f.writeln('#undef __BENCH__%s__NEEDS_UNDEF'
  444. % define)
  445. f.writeln('#undef %s' % define)
  446. f.writeln('#undef %s' % (define+'_i'))
  447. f.writeln('#endif')
  448. f.writeln()
  449. def find_runner(runner, **args):
  450. cmd = runner.copy()
  451. # run under some external command?
  452. if args.get('exec'):
  453. cmd[:0] = args['exec']
  454. # run under valgrind?
  455. if args.get('valgrind'):
  456. cmd[:0] = args['valgrind_path'] + [
  457. '--leak-check=full',
  458. '--track-origins=yes',
  459. '--error-exitcode=4',
  460. '-q']
  461. # run under perf?
  462. if args.get('perf'):
  463. cmd[:0] = args['perf_script'] + list(filter(None, [
  464. '-R',
  465. '--perf-freq=%s' % args['perf_freq']
  466. if args.get('perf_freq') else None,
  467. '--perf-period=%s' % args['perf_period']
  468. if args.get('perf_period') else None,
  469. '--perf-events=%s' % args['perf_events']
  470. if args.get('perf_events') else None,
  471. '--perf-path=%s' % args['perf_path']
  472. if args.get('perf_path') else None,
  473. '-o%s' % args['perf']]))
  474. # other context
  475. if args.get('geometry'):
  476. cmd.append('-G%s' % args['geometry'])
  477. if args.get('disk'):
  478. cmd.append('-d%s' % args['disk'])
  479. if args.get('trace'):
  480. cmd.append('-t%s' % args['trace'])
  481. if args.get('trace_backtrace'):
  482. cmd.append('--trace-backtrace')
  483. if args.get('trace_period'):
  484. cmd.append('--trace-period=%s' % args['trace_period'])
  485. if args.get('trace_freq'):
  486. cmd.append('--trace-freq=%s' % args['trace_freq'])
  487. if args.get('read_sleep'):
  488. cmd.append('--read-sleep=%s' % args['read_sleep'])
  489. if args.get('prog_sleep'):
  490. cmd.append('--prog-sleep=%s' % args['prog_sleep'])
  491. if args.get('erase_sleep'):
  492. cmd.append('--erase-sleep=%s' % args['erase_sleep'])
  493. # defines?
  494. if args.get('define'):
  495. for define in args.get('define'):
  496. cmd.append('-D%s' % define)
  497. return cmd
  498. def list_(runner, bench_ids=[], **args):
  499. cmd = find_runner(runner, **args) + bench_ids
  500. if args.get('summary'): cmd.append('--summary')
  501. if args.get('list_suites'): cmd.append('--list-suites')
  502. if args.get('list_cases'): cmd.append('--list-cases')
  503. if args.get('list_suite_paths'): cmd.append('--list-suite-paths')
  504. if args.get('list_case_paths'): cmd.append('--list-case-paths')
  505. if args.get('list_defines'): cmd.append('--list-defines')
  506. if args.get('list_permutation_defines'):
  507. cmd.append('--list-permutation-defines')
  508. if args.get('list_implicit_defines'):
  509. cmd.append('--list-implicit-defines')
  510. if args.get('list_geometries'): cmd.append('--list-geometries')
  511. if args.get('verbose'):
  512. print(' '.join(shlex.quote(c) for c in cmd))
  513. return sp.call(cmd)
  514. def find_perms(runner_, ids=[], **args):
  515. case_suites = {}
  516. expected_case_perms = co.defaultdict(lambda: 0)
  517. expected_perms = 0
  518. total_perms = 0
  519. # query cases from the runner
  520. cmd = runner_ + ['--list-cases'] + ids
  521. if args.get('verbose'):
  522. print(' '.join(shlex.quote(c) for c in cmd))
  523. proc = sp.Popen(cmd,
  524. stdout=sp.PIPE,
  525. stderr=sp.PIPE if not args.get('verbose') else None,
  526. universal_newlines=True,
  527. errors='replace',
  528. close_fds=False)
  529. pattern = re.compile(
  530. '^(?P<case>[^\s]+)'
  531. '\s+(?P<flags>[^\s]+)'
  532. '\s+(?P<filtered>\d+)/(?P<perms>\d+)')
  533. # skip the first line
  534. for line in it.islice(proc.stdout, 1, None):
  535. m = pattern.match(line)
  536. if m:
  537. filtered = int(m.group('filtered'))
  538. perms = int(m.group('perms'))
  539. expected_case_perms[m.group('case')] += filtered
  540. expected_perms += filtered
  541. total_perms += perms
  542. proc.wait()
  543. if proc.returncode != 0:
  544. if not args.get('verbose'):
  545. for line in proc.stderr:
  546. sys.stdout.write(line)
  547. sys.exit(-1)
  548. # get which suite each case belongs to via paths
  549. cmd = runner_ + ['--list-case-paths'] + ids
  550. if args.get('verbose'):
  551. print(' '.join(shlex.quote(c) for c in cmd))
  552. proc = sp.Popen(cmd,
  553. stdout=sp.PIPE,
  554. stderr=sp.PIPE if not args.get('verbose') else None,
  555. universal_newlines=True,
  556. errors='replace',
  557. close_fds=False)
  558. pattern = re.compile(
  559. '^(?P<case>[^\s]+)'
  560. '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
  561. # skip the first line
  562. for line in it.islice(proc.stdout, 1, None):
  563. m = pattern.match(line)
  564. if m:
  565. path = m.group('path')
  566. # strip path/suffix here
  567. suite = os.path.basename(path)
  568. if suite.endswith('.toml'):
  569. suite = suite[:-len('.toml')]
  570. case_suites[m.group('case')] = suite
  571. proc.wait()
  572. if proc.returncode != 0:
  573. if not args.get('verbose'):
  574. for line in proc.stderr:
  575. sys.stdout.write(line)
  576. sys.exit(-1)
  577. # figure out expected suite perms
  578. expected_suite_perms = co.defaultdict(lambda: 0)
  579. for case, suite in case_suites.items():
  580. expected_suite_perms[suite] += expected_case_perms[case]
  581. return (
  582. case_suites,
  583. expected_suite_perms,
  584. expected_case_perms,
  585. expected_perms,
  586. total_perms)
  587. def find_path(runner_, id, **args):
  588. path = None
  589. # query from runner
  590. cmd = runner_ + ['--list-case-paths', id]
  591. if args.get('verbose'):
  592. print(' '.join(shlex.quote(c) for c in cmd))
  593. proc = sp.Popen(cmd,
  594. stdout=sp.PIPE,
  595. stderr=sp.PIPE if not args.get('verbose') else None,
  596. universal_newlines=True,
  597. errors='replace',
  598. close_fds=False)
  599. pattern = re.compile(
  600. '^(?P<case>[^\s]+)'
  601. '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
  602. # skip the first line
  603. for line in it.islice(proc.stdout, 1, None):
  604. m = pattern.match(line)
  605. if m and path is None:
  606. path_ = m.group('path')
  607. lineno = int(m.group('lineno'))
  608. path = (path_, lineno)
  609. proc.wait()
  610. if proc.returncode != 0:
  611. if not args.get('verbose'):
  612. for line in proc.stderr:
  613. sys.stdout.write(line)
  614. sys.exit(-1)
  615. return path
  616. def find_defines(runner_, id, **args):
  617. # query permutation defines from runner
  618. cmd = runner_ + ['--list-permutation-defines', id]
  619. if args.get('verbose'):
  620. print(' '.join(shlex.quote(c) for c in cmd))
  621. proc = sp.Popen(cmd,
  622. stdout=sp.PIPE,
  623. stderr=sp.PIPE if not args.get('verbose') else None,
  624. universal_newlines=True,
  625. errors='replace',
  626. close_fds=False)
  627. defines = co.OrderedDict()
  628. pattern = re.compile('^(?P<define>\w+)=(?P<value>.+)')
  629. for line in proc.stdout:
  630. m = pattern.match(line)
  631. if m:
  632. define = m.group('define')
  633. value = m.group('value')
  634. defines[define] = value
  635. proc.wait()
  636. if proc.returncode != 0:
  637. if not args.get('verbose'):
  638. for line in proc.stderr:
  639. sys.stdout.write(line)
  640. sys.exit(-1)
  641. return defines
  642. # Thread-safe CSV writer
  643. class BenchOutput:
  644. def __init__(self, path, head=None, tail=None):
  645. self.f = openio(path, 'w+', 1)
  646. self.lock = th.Lock()
  647. self.head = head or []
  648. self.tail = tail or []
  649. self.writer = csv.DictWriter(self.f, self.head + self.tail)
  650. self.rows = []
  651. def close(self):
  652. self.f.close()
  653. def __enter__(self):
  654. return self
  655. def __exit__(self, *_):
  656. self.f.close()
  657. def writerow(self, row):
  658. with self.lock:
  659. self.rows.append(row)
  660. if all(k in self.head or k in self.tail for k in row.keys()):
  661. # can simply append
  662. self.writer.writerow(row)
  663. else:
  664. # need to rewrite the file
  665. self.head.extend(row.keys() - (self.head + self.tail))
  666. self.f.seek(0)
  667. self.f.truncate()
  668. self.writer = csv.DictWriter(self.f, self.head + self.tail)
  669. self.writer.writeheader()
  670. for row in self.rows:
  671. self.writer.writerow(row)
  672. # A bench failure
  673. class BenchFailure(Exception):
  674. def __init__(self, id, returncode, stdout, assert_=None):
  675. self.id = id
  676. self.returncode = returncode
  677. self.stdout = stdout
  678. self.assert_ = assert_
  679. def run_stage(name, runner_, ids, stdout_, trace_, output_, **args):
  680. # get expected suite/case/perm counts
  681. (case_suites,
  682. expected_suite_perms,
  683. expected_case_perms,
  684. expected_perms,
  685. total_perms) = find_perms(runner_, ids, **args)
  686. passed_suite_perms = co.defaultdict(lambda: 0)
  687. passed_case_perms = co.defaultdict(lambda: 0)
  688. passed_perms = 0
  689. readed = 0
  690. proged = 0
  691. erased = 0
  692. failures = []
  693. killed = False
  694. pattern = re.compile('^(?:'
  695. '(?P<op>running|finished|skipped|powerloss)'
  696. ' (?P<id>(?P<case>[^:]+)[^\s]*)'
  697. '(?: (?P<readed>\d+))?'
  698. '(?: (?P<proged>\d+))?'
  699. '(?: (?P<erased>\d+))?'
  700. '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
  701. ' *(?P<message>.*)'
  702. ')$')
  703. locals = th.local()
  704. children = set()
  705. def run_runner(runner_, ids=[]):
  706. nonlocal passed_suite_perms
  707. nonlocal passed_case_perms
  708. nonlocal passed_perms
  709. nonlocal readed
  710. nonlocal proged
  711. nonlocal erased
  712. nonlocal locals
  713. # run the benches!
  714. cmd = runner_ + ids
  715. if args.get('verbose'):
  716. print(' '.join(shlex.quote(c) for c in cmd))
  717. mpty, spty = pty.openpty()
  718. proc = sp.Popen(cmd, stdout=spty, stderr=spty, close_fds=False)
  719. os.close(spty)
  720. children.add(proc)
  721. mpty = os.fdopen(mpty, 'r', 1)
  722. last_id = None
  723. last_stdout = co.deque(maxlen=args.get('context', 5) + 1)
  724. last_assert = None
  725. try:
  726. while True:
  727. # parse a line for state changes
  728. try:
  729. line = mpty.readline()
  730. except OSError as e:
  731. if e.errno != errno.EIO:
  732. raise
  733. break
  734. if not line:
  735. break
  736. last_stdout.append(line)
  737. if stdout_:
  738. try:
  739. stdout_.write(line)
  740. stdout_.flush()
  741. except BrokenPipeError:
  742. pass
  743. m = pattern.match(line)
  744. if m:
  745. op = m.group('op') or m.group('op_')
  746. if op == 'running':
  747. locals.seen_perms += 1
  748. last_id = m.group('id')
  749. last_stdout.clear()
  750. last_assert = None
  751. elif op == 'finished':
  752. case = m.group('case')
  753. suite = case_suites[case]
  754. readed_ = int(m.group('readed'))
  755. proged_ = int(m.group('proged'))
  756. erased_ = int(m.group('erased'))
  757. passed_suite_perms[suite] += 1
  758. passed_case_perms[case] += 1
  759. passed_perms += 1
  760. readed += readed_
  761. proged += proged_
  762. erased += erased_
  763. if output_:
  764. # get defines and write to csv
  765. defines = find_defines(
  766. runner_, m.group('id'), **args)
  767. output_.writerow({
  768. 'suite': suite,
  769. 'case': case,
  770. 'bench_readed': readed_,
  771. 'bench_proged': proged_,
  772. 'bench_erased': erased_,
  773. **defines})
  774. elif op == 'skipped':
  775. locals.seen_perms += 1
  776. elif op == 'assert':
  777. last_assert = (
  778. m.group('path'),
  779. int(m.group('lineno')),
  780. m.group('message'))
  781. # go ahead and kill the process, aborting takes a while
  782. if args.get('keep_going'):
  783. proc.kill()
  784. except KeyboardInterrupt:
  785. raise BenchFailure(last_id, 1, list(last_stdout))
  786. finally:
  787. children.remove(proc)
  788. mpty.close()
  789. proc.wait()
  790. if proc.returncode != 0:
  791. raise BenchFailure(
  792. last_id,
  793. proc.returncode,
  794. list(last_stdout),
  795. last_assert)
  796. def run_job(runner_, ids=[], start=None, step=None):
  797. nonlocal failures
  798. nonlocal killed
  799. nonlocal locals
  800. start = start or 0
  801. step = step or 1
  802. while start < total_perms:
  803. job_runner = runner_.copy()
  804. if args.get('isolate') or args.get('valgrind'):
  805. job_runner.append('-s%s,%s,%s' % (start, start+step, step))
  806. else:
  807. job_runner.append('-s%s,,%s' % (start, step))
  808. try:
  809. # run the benches
  810. locals.seen_perms = 0
  811. run_runner(job_runner, ids)
  812. assert locals.seen_perms > 0
  813. start += locals.seen_perms*step
  814. except BenchFailure as failure:
  815. # keep track of failures
  816. if output_:
  817. case, _ = failure.id.split(':', 1)
  818. suite = case_suites[case]
  819. # get defines and write to csv
  820. defines = find_defines(runner_, failure.id, **args)
  821. output_.writerow({
  822. 'suite': suite,
  823. 'case': case,
  824. **defines})
  825. # race condition for multiple failures?
  826. if failures and not args.get('keep_going'):
  827. break
  828. failures.append(failure)
  829. if args.get('keep_going') and not killed:
  830. # resume after failed bench
  831. assert locals.seen_perms > 0
  832. start += locals.seen_perms*step
  833. continue
  834. else:
  835. # stop other benches
  836. killed = True
  837. for child in children.copy():
  838. child.kill()
  839. break
  840. # parallel jobs?
  841. runners = []
  842. if 'jobs' in args:
  843. for job in range(args['jobs']):
  844. runners.append(th.Thread(
  845. target=run_job, args=(runner_, ids, job, args['jobs']),
  846. daemon=True))
  847. else:
  848. runners.append(th.Thread(
  849. target=run_job, args=(runner_, ids, None, None),
  850. daemon=True))
  851. def print_update(done):
  852. if not args.get('verbose') and (args['color'] or done):
  853. sys.stdout.write('%s%srunning %s%s:%s %s%s' % (
  854. '\r\x1b[K' if args['color'] else '',
  855. '\x1b[?7l' if not done else '',
  856. ('\x1b[34m' if not failures else '\x1b[31m')
  857. if args['color'] else '',
  858. name,
  859. '\x1b[m' if args['color'] else '',
  860. ', '.join(filter(None, [
  861. '%d/%d suites' % (
  862. sum(passed_suite_perms[k] == v
  863. for k, v in expected_suite_perms.items()),
  864. len(expected_suite_perms))
  865. if (not args.get('by_suites')
  866. and not args.get('by_cases')) else None,
  867. '%d/%d cases' % (
  868. sum(passed_case_perms[k] == v
  869. for k, v in expected_case_perms.items()),
  870. len(expected_case_perms))
  871. if not args.get('by_cases') else None,
  872. '%d/%d perms' % (passed_perms, expected_perms),
  873. '%s%d/%d failures%s' % (
  874. '\x1b[31m' if args['color'] else '',
  875. len(failures),
  876. expected_perms,
  877. '\x1b[m' if args['color'] else '')
  878. if failures else None])),
  879. '\x1b[?7h' if not done else '\n'))
  880. sys.stdout.flush()
  881. for r in runners:
  882. r.start()
  883. try:
  884. while any(r.is_alive() for r in runners):
  885. time.sleep(0.01)
  886. print_update(False)
  887. except KeyboardInterrupt:
  888. # this is handled by the runner threads, we just
  889. # need to not abort here
  890. killed = True
  891. finally:
  892. print_update(True)
  893. for r in runners:
  894. r.join()
  895. return (
  896. expected_perms,
  897. passed_perms,
  898. readed,
  899. proged,
  900. erased,
  901. failures,
  902. killed)
  903. def run(runner, bench_ids=[], **args):
  904. # query runner for benches
  905. runner_ = find_runner(runner, **args)
  906. print('using runner: %s' % ' '.join(shlex.quote(c) for c in runner_))
  907. (_,
  908. expected_suite_perms,
  909. expected_case_perms,
  910. expected_perms,
  911. total_perms) = find_perms(runner_, bench_ids, **args)
  912. print('found %d suites, %d cases, %d/%d permutations' % (
  913. len(expected_suite_perms),
  914. len(expected_case_perms),
  915. expected_perms,
  916. total_perms))
  917. print()
  918. # automatic job detection?
  919. if args.get('jobs') == 0:
  920. args['jobs'] = len(os.sched_getaffinity(0))
  921. # truncate and open logs here so they aren't disconnected between benches
  922. stdout = None
  923. if args.get('stdout'):
  924. stdout = openio(args['stdout'], 'w', 1)
  925. trace = None
  926. if args.get('trace'):
  927. trace = openio(args['trace'], 'w', 1)
  928. output = None
  929. if args.get('output'):
  930. output = BenchOutput(args['output'],
  931. ['suite', 'case'],
  932. ['bench_readed', 'bench_proged', 'bench_erased'])
  933. # measure runtime
  934. start = time.time()
  935. # spawn runners
  936. expected = 0
  937. passed = 0
  938. readed = 0
  939. proged = 0
  940. erased = 0
  941. failures = []
  942. for by in (bench_ids if bench_ids
  943. else expected_case_perms.keys() if args.get('by_cases')
  944. else expected_suite_perms.keys() if args.get('by_suites')
  945. else [None]):
  946. # spawn jobs for stage
  947. (expected_,
  948. passed_,
  949. readed_,
  950. proged_,
  951. erased_,
  952. failures_,
  953. killed) = run_stage(
  954. by or 'benches',
  955. runner_,
  956. [by] if by is not None else [],
  957. stdout,
  958. trace,
  959. output,
  960. **args)
  961. # collect passes/failures
  962. expected += expected_
  963. passed += passed_
  964. readed += readed_
  965. proged += proged_
  966. erased += erased_
  967. failures.extend(failures_)
  968. if (failures and not args.get('keep_going')) or killed:
  969. break
  970. stop = time.time()
  971. if stdout:
  972. try:
  973. stdout.close()
  974. except BrokenPipeError:
  975. pass
  976. if trace:
  977. try:
  978. trace.close()
  979. except BrokenPipeError:
  980. pass
  981. if output:
  982. output.close()
  983. # show summary
  984. print()
  985. print('%sdone:%s %s' % (
  986. ('\x1b[34m' if not failures else '\x1b[31m')
  987. if args['color'] else '',
  988. '\x1b[m' if args['color'] else '',
  989. ', '.join(filter(None, [
  990. '%d readed' % readed,
  991. '%d proged' % proged,
  992. '%d erased' % erased,
  993. 'in %.2fs' % (stop-start)]))))
  994. print()
  995. # print each failure
  996. for failure in failures:
  997. assert failure.id is not None, '%s broken? %r' % (
  998. ' '.join(shlex.quote(c) for c in runner_),
  999. failure)
  1000. # get some extra info from runner
  1001. path, lineno = find_path(runner_, failure.id, **args)
  1002. defines = find_defines(runner_, failure.id, **args)
  1003. # show summary of failure
  1004. print('%s%s:%d:%sfailure:%s %s%s failed' % (
  1005. '\x1b[01m' if args['color'] else '',
  1006. path, lineno,
  1007. '\x1b[01;31m' if args['color'] else '',
  1008. '\x1b[m' if args['color'] else '',
  1009. failure.id,
  1010. ' (%s)' % ', '.join('%s=%s' % (k,v) for k,v in defines.items())
  1011. if defines else ''))
  1012. if failure.stdout:
  1013. stdout = failure.stdout
  1014. if failure.assert_ is not None:
  1015. stdout = stdout[:-1]
  1016. for line in stdout[-args.get('context', 5):]:
  1017. sys.stdout.write(line)
  1018. if failure.assert_ is not None:
  1019. path, lineno, message = failure.assert_
  1020. print('%s%s:%d:%sassert:%s %s' % (
  1021. '\x1b[01m' if args['color'] else '',
  1022. path, lineno,
  1023. '\x1b[01;31m' if args['color'] else '',
  1024. '\x1b[m' if args['color'] else '',
  1025. message))
  1026. with open(path) as f:
  1027. line = next(it.islice(f, lineno-1, None)).strip('\n')
  1028. print(line)
  1029. print()
  1030. # drop into gdb?
  1031. if failures and (args.get('gdb')
  1032. or args.get('gdb_case')
  1033. or args.get('gdb_main')):
  1034. failure = failures[0]
  1035. cmd = runner_ + [failure.id]
  1036. if args.get('gdb_main'):
  1037. # we don't really need the case breakpoint here, but it
  1038. # can be helpful
  1039. path, lineno = find_path(runner_, failure.id, **args)
  1040. cmd[:0] = args['gdb_path'] + [
  1041. '-ex', 'break main',
  1042. '-ex', 'break %s:%d' % (path, lineno),
  1043. '-ex', 'run',
  1044. '--args']
  1045. elif args.get('gdb_case'):
  1046. path, lineno = find_path(runner_, failure.id, **args)
  1047. cmd[:0] = args['gdb_path'] + [
  1048. '-ex', 'break %s:%d' % (path, lineno),
  1049. '-ex', 'run',
  1050. '--args']
  1051. elif failure.assert_ is not None:
  1052. cmd[:0] = args['gdb_path'] + [
  1053. '-ex', 'run',
  1054. '-ex', 'frame function raise',
  1055. '-ex', 'up 2',
  1056. '--args']
  1057. else:
  1058. cmd[:0] = args['gdb_path'] + [
  1059. '-ex', 'run',
  1060. '--args']
  1061. # exec gdb interactively
  1062. if args.get('verbose'):
  1063. print(' '.join(shlex.quote(c) for c in cmd))
  1064. os.execvp(cmd[0], cmd)
  1065. return 1 if failures else 0
  1066. def main(**args):
  1067. # figure out what color should be
  1068. if args.get('color') == 'auto':
  1069. args['color'] = sys.stdout.isatty()
  1070. elif args.get('color') == 'always':
  1071. args['color'] = True
  1072. else:
  1073. args['color'] = False
  1074. if args.get('compile'):
  1075. return compile(**args)
  1076. elif (args.get('summary')
  1077. or args.get('list_suites')
  1078. or args.get('list_cases')
  1079. or args.get('list_suite_paths')
  1080. or args.get('list_case_paths')
  1081. or args.get('list_defines')
  1082. or args.get('list_permutation_defines')
  1083. or args.get('list_implicit_defines')
  1084. or args.get('list_geometries')):
  1085. return list_(**args)
  1086. else:
  1087. return run(**args)
  1088. if __name__ == "__main__":
  1089. import argparse
  1090. import sys
  1091. argparse.ArgumentParser._handle_conflict_ignore = lambda *_: None
  1092. argparse._ArgumentGroup._handle_conflict_ignore = lambda *_: None
  1093. parser = argparse.ArgumentParser(
  1094. description="Build and run benches.",
  1095. allow_abbrev=False,
  1096. conflict_handler='ignore')
  1097. parser.add_argument(
  1098. '-v', '--verbose',
  1099. action='store_true',
  1100. help="Output commands that run behind the scenes.")
  1101. parser.add_argument(
  1102. '--color',
  1103. choices=['never', 'always', 'auto'],
  1104. default='auto',
  1105. help="When to use terminal colors. Defaults to 'auto'.")
  1106. # bench flags
  1107. bench_parser = parser.add_argument_group('bench options')
  1108. bench_parser.add_argument(
  1109. 'runner',
  1110. nargs='?',
  1111. type=lambda x: x.split(),
  1112. help="Bench runner to use for benching. Defaults to %r." % RUNNER_PATH)
  1113. bench_parser.add_argument(
  1114. 'bench_ids',
  1115. nargs='*',
  1116. help="Description of benches to run.")
  1117. bench_parser.add_argument(
  1118. '-Y', '--summary',
  1119. action='store_true',
  1120. help="Show quick summary.")
  1121. bench_parser.add_argument(
  1122. '-l', '--list-suites',
  1123. action='store_true',
  1124. help="List bench suites.")
  1125. bench_parser.add_argument(
  1126. '-L', '--list-cases',
  1127. action='store_true',
  1128. help="List bench cases.")
  1129. bench_parser.add_argument(
  1130. '--list-suite-paths',
  1131. action='store_true',
  1132. help="List the path for each bench suite.")
  1133. bench_parser.add_argument(
  1134. '--list-case-paths',
  1135. action='store_true',
  1136. help="List the path and line number for each bench case.")
  1137. bench_parser.add_argument(
  1138. '--list-defines',
  1139. action='store_true',
  1140. help="List all defines in this bench-runner.")
  1141. bench_parser.add_argument(
  1142. '--list-permutation-defines',
  1143. action='store_true',
  1144. help="List explicit defines in this bench-runner.")
  1145. bench_parser.add_argument(
  1146. '--list-implicit-defines',
  1147. action='store_true',
  1148. help="List implicit defines in this bench-runner.")
  1149. bench_parser.add_argument(
  1150. '--list-geometries',
  1151. action='store_true',
  1152. help="List the available disk geometries.")
  1153. bench_parser.add_argument(
  1154. '-D', '--define',
  1155. action='append',
  1156. help="Override a bench define.")
  1157. bench_parser.add_argument(
  1158. '-G', '--geometry',
  1159. help="Comma-separated list of disk geometries to bench.")
  1160. bench_parser.add_argument(
  1161. '-d', '--disk',
  1162. help="Direct block device operations to this file.")
  1163. bench_parser.add_argument(
  1164. '-t', '--trace',
  1165. help="Direct trace output to this file.")
  1166. bench_parser.add_argument(
  1167. '--trace-backtrace',
  1168. action='store_true',
  1169. help="Include a backtrace with every trace statement.")
  1170. bench_parser.add_argument(
  1171. '--trace-period',
  1172. help="Sample trace output at this period in cycles.")
  1173. bench_parser.add_argument(
  1174. '--trace-freq',
  1175. help="Sample trace output at this frequency in hz.")
  1176. bench_parser.add_argument(
  1177. '-O', '--stdout',
  1178. help="Direct stdout to this file. Note stderr is already merged here.")
  1179. bench_parser.add_argument(
  1180. '-o', '--output',
  1181. help="CSV file to store results.")
  1182. bench_parser.add_argument(
  1183. '--read-sleep',
  1184. help="Artificial read delay in seconds.")
  1185. bench_parser.add_argument(
  1186. '--prog-sleep',
  1187. help="Artificial prog delay in seconds.")
  1188. bench_parser.add_argument(
  1189. '--erase-sleep',
  1190. help="Artificial erase delay in seconds.")
  1191. bench_parser.add_argument(
  1192. '-j', '--jobs',
  1193. nargs='?',
  1194. type=lambda x: int(x, 0),
  1195. const=0,
  1196. help="Number of parallel runners to run. 0 runs one runner per core.")
  1197. bench_parser.add_argument(
  1198. '-k', '--keep-going',
  1199. action='store_true',
  1200. help="Don't stop on first error.")
  1201. bench_parser.add_argument(
  1202. '-i', '--isolate',
  1203. action='store_true',
  1204. help="Run each bench permutation in a separate process.")
  1205. bench_parser.add_argument(
  1206. '-b', '--by-suites',
  1207. action='store_true',
  1208. help="Step through benches by suite.")
  1209. bench_parser.add_argument(
  1210. '-B', '--by-cases',
  1211. action='store_true',
  1212. help="Step through benches by case.")
  1213. bench_parser.add_argument(
  1214. '--context',
  1215. type=lambda x: int(x, 0),
  1216. default=5,
  1217. help="Show this many lines of stdout on bench failure. "
  1218. "Defaults to 5.")
  1219. bench_parser.add_argument(
  1220. '--gdb',
  1221. action='store_true',
  1222. help="Drop into gdb on bench failure.")
  1223. bench_parser.add_argument(
  1224. '--gdb-case',
  1225. action='store_true',
  1226. help="Drop into gdb on bench failure but stop at the beginning "
  1227. "of the failing bench case.")
  1228. bench_parser.add_argument(
  1229. '--gdb-main',
  1230. action='store_true',
  1231. help="Drop into gdb on bench failure but stop at the beginning "
  1232. "of main.")
  1233. bench_parser.add_argument(
  1234. '--gdb-path',
  1235. type=lambda x: x.split(),
  1236. default=GDB_PATH,
  1237. help="Path to the gdb executable, may include flags. "
  1238. "Defaults to %r." % GDB_PATH)
  1239. bench_parser.add_argument(
  1240. '--exec',
  1241. type=lambda e: e.split(),
  1242. help="Run under another executable.")
  1243. bench_parser.add_argument(
  1244. '--valgrind',
  1245. action='store_true',
  1246. help="Run under Valgrind to find memory errors. Implicitly sets "
  1247. "--isolate.")
  1248. bench_parser.add_argument(
  1249. '--valgrind-path',
  1250. type=lambda x: x.split(),
  1251. default=VALGRIND_PATH,
  1252. help="Path to the Valgrind executable, may include flags. "
  1253. "Defaults to %r." % VALGRIND_PATH)
  1254. bench_parser.add_argument(
  1255. '-p', '--perf',
  1256. help="Run under Linux's perf to sample performance counters, writing "
  1257. "samples to this file.")
  1258. bench_parser.add_argument(
  1259. '--perf-freq',
  1260. help="perf sampling frequency. This is passed directly to the perf "
  1261. "script.")
  1262. bench_parser.add_argument(
  1263. '--perf-period',
  1264. help="perf sampling period. This is passed directly to the perf "
  1265. "script.")
  1266. bench_parser.add_argument(
  1267. '--perf-events',
  1268. help="perf events to record. This is passed directly to the perf "
  1269. "script.")
  1270. bench_parser.add_argument(
  1271. '--perf-script',
  1272. type=lambda x: x.split(),
  1273. default=PERF_SCRIPT,
  1274. help="Path to the perf script to use. Defaults to %r." % PERF_SCRIPT)
  1275. bench_parser.add_argument(
  1276. '--perf-path',
  1277. type=lambda x: x.split(),
  1278. help="Path to the perf executable, may include flags. This is passed "
  1279. "directly to the perf script")
  1280. # compilation flags
  1281. comp_parser = parser.add_argument_group('compilation options')
  1282. comp_parser.add_argument(
  1283. 'bench_paths',
  1284. nargs='*',
  1285. help="Description of *.toml files to compile. May be a directory "
  1286. "or a list of paths.")
  1287. comp_parser.add_argument(
  1288. '-c', '--compile',
  1289. action='store_true',
  1290. help="Compile a bench suite or source file.")
  1291. comp_parser.add_argument(
  1292. '-s', '--source',
  1293. help="Source file to compile, possibly injecting internal benches.")
  1294. comp_parser.add_argument(
  1295. '--include',
  1296. default=HEADER_PATH,
  1297. help="Inject this header file into every compiled bench file. "
  1298. "Defaults to %r." % HEADER_PATH)
  1299. comp_parser.add_argument(
  1300. '-o', '--output',
  1301. help="Output file.")
  1302. # runner/bench_paths overlap, so need to do some munging here
  1303. args = parser.parse_intermixed_args()
  1304. args.bench_paths = [' '.join(args.runner or [])] + args.bench_ids
  1305. args.runner = args.runner or [RUNNER_PATH]
  1306. sys.exit(main(**{k: v
  1307. for k, v in vars(args).items()
  1308. if v is not None}))