run-tests.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import argparse
  2. import ConfigParser
  3. import socket
  4. import sys
  5. import os
  6. import platform
  7. import multiprocessing
  8. import signal
  9. from toolset.benchmark.benchmarker import Benchmarker
  10. from toolset.utils.scaffolding import Scaffolding
  11. from toolset.utils.initializer import initialize
  12. from toolset.utils import cleaner
  13. from toolset.utils.results_helper import Results
  14. from toolset.utils.benchmark_config import BenchmarkConfig
  15. from toolset.utils import docker_helper
  16. from toolset.utils.metadata_helper import gather_tests
  17. from toolset.utils.output_helper import log
  18. from ast import literal_eval
  19. # Enable cross-platform colored output
  20. from colorama import init
  21. init()
  22. class StoreSeqAction(argparse.Action):
  23. '''
  24. Helper class for parsing a sequence from the command line
  25. '''
  26. def __init__(self, option_strings, dest, nargs=None, **kwargs):
  27. super(StoreSeqAction, self).__init__(
  28. option_strings, dest, type=str, **kwargs)
  29. def __call__(self, parser, namespace, values, option_string=None):
  30. setattr(namespace, self.dest, self.parse_seq(values))
  31. def parse_seq(self, argument):
  32. result = argument.split(',')
  33. sequences = [x for x in result if ":" in x]
  34. for sequence in sequences:
  35. try:
  36. (start, step, end) = sequence.split(':')
  37. except ValueError:
  38. log(" Invalid: {!s}".format(sequence))
  39. log(" Requires start:step:end, e.g. 1:2:10")
  40. raise
  41. result.remove(sequence)
  42. result = result + range(int(start), int(end), int(step))
  43. return [abs(int(item)) for item in result]
  44. def __stop(signal, frame):
  45. '''
  46. Method called on SIGTERM to stop all running containers
  47. '''
  48. docker_helper.stop()
  49. sys.exit(0)
  50. signal.signal(signal.SIGTERM, __stop)
  51. ###################################################################################################
  52. # Main
  53. ###################################################################################################
  54. def main(argv=None):
  55. '''
  56. Runs the program. There are three ways to pass arguments
  57. 1) environment variables TFB_*
  58. 2) configuration file benchmark.cfg
  59. 3) command line flags
  60. In terms of precedence, 3 > 2 > 1, so config file trumps environment variables
  61. but command line flags have the final say
  62. '''
  63. # Do argv default this way, as doing it in the functional declaration sets it at compile time
  64. if argv is None:
  65. argv = sys.argv
  66. # 'Ubuntu', '14.04', 'trusty' respectively
  67. os.environ['TFB_DISTRIB_ID'], os.environ[
  68. 'TFB_DISTRIB_RELEASE'], os.environ[
  69. 'TFB_DISTRIB_CODENAME'] = platform.linux_distribution()
  70. # App server cpu count
  71. os.environ['CPU_COUNT'] = str(multiprocessing.cpu_count())
  72. conf_parser = argparse.ArgumentParser(
  73. description=__doc__,
  74. formatter_class=argparse.RawDescriptionHelpFormatter,
  75. add_help=False)
  76. conf_parser.add_argument(
  77. '--conf_file',
  78. default='benchmark.cfg',
  79. metavar='FILE',
  80. help=
  81. 'Optional configuration file to provide argument defaults. All config options can be overridden using the command line.'
  82. )
  83. args, remaining_argv = conf_parser.parse_known_args()
  84. defaults = {}
  85. try:
  86. if not os.path.exists(
  87. os.path.join(
  88. os.environ['FWROOT'],
  89. args.conf_file)) and not os.path.exists(
  90. os.path.join(os.environ['FWROOT'] + 'benchmark.cfg')):
  91. log("No config file found. Aborting!")
  92. exit(1)
  93. with open(os.path.join(os.environ['FWROOT'], args.conf_file)):
  94. config = ConfigParser.SafeConfigParser()
  95. config.read([os.path.join(os.environ['FWROOT'], args.conf_file)])
  96. defaults.update(dict(config.items("Defaults")))
  97. # Convert strings into proper python types
  98. for k, v in defaults.items():
  99. try:
  100. defaults[k] = literal_eval(v)
  101. except Exception:
  102. pass
  103. except IOError:
  104. log("Configuration file not found!")
  105. exit(1)
  106. ##########################################################
  107. # Set up default values
  108. ##########################################################
  109. # Verify and massage options
  110. if defaults['client_user'] is None or defaults['client_host'] is None:
  111. log("client_user and client_host are required!")
  112. log("Please check your configuration file.")
  113. log("Aborting!")
  114. exit(1)
  115. if defaults['database_user'] is None:
  116. defaults['database_user'] = defaults['client_user']
  117. if defaults['database_host'] is None:
  118. defaults['database_host'] = defaults['client_host']
  119. if defaults['server_host'] is None:
  120. defaults['server_host'] = defaults['client_host']
  121. if defaults['ulimit'] is None:
  122. defaults['ulimit'] = 200000
  123. os.environ['ULIMIT'] = str(defaults['ulimit'])
  124. ##########################################################
  125. # Set up argument parser
  126. ##########################################################
  127. parser = argparse.ArgumentParser(
  128. description="Install or run the Framework Benchmarks test suite.",
  129. parents=[conf_parser],
  130. formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  131. epilog=
  132. '''If an argument includes (type int-sequence), then it accepts integer lists in multiple forms.
  133. Using a single number e.g. 5 will create a list [5]. Using commas will create a list containing those
  134. values e.g. 1,3,6 creates [1, 3, 6]. Using three colon-separated numbers of start:step:end will create a
  135. list, using the semantics of python's range function, e.g. 1:3:15 creates [1, 4, 7, 10, 13] while
  136. 0:1:5 creates [0, 1, 2, 3, 4]
  137. ''')
  138. # Install options
  139. parser.add_argument(
  140. '--init',
  141. action='store_true',
  142. default=False,
  143. help='Initializes the benchmark environment')
  144. # Suite options
  145. parser.add_argument(
  146. '--build',
  147. nargs='+',
  148. help='Builds the dockerfile(s) for the given test(s)')
  149. parser.add_argument(
  150. '--clean',
  151. action='store_true',
  152. default=False,
  153. help='Removes the results directory')
  154. parser.add_argument(
  155. '--new',
  156. action='store_true',
  157. default=False,
  158. help='Initialize a new framework test')
  159. parser.add_argument(
  160. '-v',
  161. '--verbose',
  162. action='store_true',
  163. default=False,
  164. help=
  165. 'Causes the configuration to print before any other commands are executed.'
  166. )
  167. parser.add_argument(
  168. '--quiet',
  169. action='store_true',
  170. default=False,
  171. help=
  172. 'Only print a limited set of messages to stdout, keep the bulk of messages in log files only'
  173. )
  174. parser.add_argument(
  175. '--results-name',
  176. help='Gives a name to this set of results, formatted as a date',
  177. default='(unspecified, datetime = %Y-%m-%d %H:%M:%S)')
  178. parser.add_argument(
  179. '--results-environment',
  180. help='Describes the environment in which these results were gathered',
  181. default='(unspecified, hostname = %s)' % socket.gethostname())
  182. parser.add_argument(
  183. '--results-upload-uri',
  184. default=None,
  185. help=
  186. 'A URI where the in-progress results.json file will be POSTed periodically'
  187. )
  188. parser.add_argument(
  189. '--parse',
  190. help=
  191. 'Parses the results of the given timestamp and merges that with the latest results'
  192. )
  193. # Test options
  194. parser.add_argument('--test', nargs='+', help='names of tests to run')
  195. parser.add_argument(
  196. '--test-dir',
  197. nargs='+',
  198. dest='test_dir',
  199. help='name of framework directory containing all tests to run')
  200. parser.add_argument(
  201. '--test-lang',
  202. nargs='+',
  203. dest='test_lang',
  204. help='name of language directory containing all tests to run')
  205. parser.add_argument(
  206. '--exclude', nargs='+', help='names of tests to exclude')
  207. parser.add_argument(
  208. '--type',
  209. choices=[
  210. 'all', 'json', 'db', 'query', 'cached_query', 'fortune', 'update',
  211. 'plaintext'
  212. ],
  213. default='all',
  214. help='which type of test to run')
  215. parser.add_argument(
  216. '-m',
  217. '--mode',
  218. choices=['benchmark', 'verify', 'debug'],
  219. default='benchmark',
  220. help=
  221. 'verify mode will only start up the tests, curl the urls and shutdown. debug mode will skip verification and leave the server running.'
  222. )
  223. parser.add_argument(
  224. '--list-tests',
  225. action='store_true',
  226. default=False,
  227. help='lists all the known tests that can run')
  228. # Benchmark options
  229. parser.add_argument(
  230. '--duration',
  231. default=15,
  232. help='Time in seconds that each test should run for.')
  233. parser.add_argument(
  234. '--sleep',
  235. type=int,
  236. default=60,
  237. help=
  238. 'the amount of time to sleep after starting each test to allow the server to start up.'
  239. )
  240. parser.set_defaults(**defaults)
  241. # Must do this after add, or each option's default will override the configuration file default
  242. args = parser.parse_args(remaining_argv)
  243. config = BenchmarkConfig(vars(args))
  244. results = Results(config)
  245. if config.new:
  246. Scaffolding()
  247. elif config.init:
  248. initialize(config)
  249. elif config.build:
  250. docker_helper.build(config, config.build)
  251. elif config.clean:
  252. cleaner.clean(results)
  253. docker_helper.clean(config)
  254. elif config.list_tests:
  255. all_tests = gather_tests(benchmarker_config=config)
  256. for test in all_tests:
  257. log(test.name)
  258. elif config.parse != None:
  259. # TODO: broken
  260. all_tests = gather_tests(benchmarker_config=config)
  261. for test in all_tests:
  262. test.parse_all()
  263. results.parse(all_tests)
  264. else:
  265. benchmarker = Benchmarker(config, results)
  266. if not benchmarker.run():
  267. return 1
  268. return 0
  269. if __name__ == "__main__":
  270. sys.exit(main())