run-tests.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env python
  2. import argparse
  3. import ConfigParser
  4. import sys
  5. import os
  6. import multiprocessing
  7. import logging
  8. log = logging.getLogger('run-tests')
  9. import subprocess
  10. from benchmark.benchmarker import Benchmarker
  11. from setup.linux.unbuffered import Unbuffered
  12. from setup.linux import setup_util
  13. ###################################################################################################
  14. # Main
  15. ###################################################################################################
  16. def main(argv=None):
  17. ''' Runs the program. There are three ways to pass arguments
  18. 1) environment variables TFB_*
  19. 2) configuration file benchmark.cfg
  20. 3) command line flags
  21. In terms of precedence, 3 > 2 > 1, so config file trumps environment variables
  22. but command line flags have the final say
  23. '''
  24. # Do argv default this way, as doing it in the functional declaration sets it at compile time
  25. if argv is None:
  26. argv = sys.argv
  27. # Enable unbuffered output so messages will appear in the proper order with subprocess output.
  28. sys.stdout=Unbuffered(sys.stdout)
  29. # Update python environment
  30. # 1) Ensure the current directory (which should be the benchmark home directory) is in the path so that the tests can be imported.
  31. sys.path.append('.')
  32. # 2) Ensure toolset/setup/linux is in the path so that the tests can "import setup_util".
  33. sys.path.append('toolset/setup/linux')
  34. sys.path.append('toolset/benchmark')
  35. # Update environment for shell scripts
  36. fwroot = setup_util.get_fwroot()
  37. if not fwroot:
  38. fwroot = os.getcwd()
  39. setup_util.replace_environ(config='config/benchmark_profile', root=fwroot)
  40. print "FWROOT is %s"%setup_util.get_fwroot()
  41. conf_parser = argparse.ArgumentParser(
  42. description=__doc__,
  43. formatter_class=argparse.RawDescriptionHelpFormatter,
  44. add_help=False)
  45. conf_parser.add_argument('--conf_file', default='benchmark.cfg', metavar='FILE', help='Optional configuration file to provide argument defaults. All config options can be overridden using the command line.')
  46. args, remaining_argv = conf_parser.parse_known_args()
  47. try:
  48. with open (args.conf_file):
  49. config = ConfigParser.SafeConfigParser()
  50. config.read([os.getcwd() + '/' + args.conf_file])
  51. defaults = dict(config.items("Defaults"))
  52. except IOError:
  53. if args.conf_file != 'benchmark.cfg':
  54. log.warn('Configuration file not found!')
  55. defaults = { "client-host":"localhost"}
  56. ##########################################################
  57. # Set up default values
  58. ##########################################################
  59. serverHost = os.environ.get('TFB_SERVER_HOST')
  60. clientHost = os.environ.get('TFB_CLIENT_HOST')
  61. clientUser = os.environ.get('TFB_CLIENT_USER')
  62. clientIden = os.environ.get('TFB_CLIENT_IDENTITY_FILE')
  63. databaHost = os.getenv('TFB_DATABASE_HOST', clientHost)
  64. databaUser = os.getenv('TFB_DATABASE_USER', clientUser)
  65. dbIdenFile = os.getenv('TFB_DATABASE_IDENTITY_FILE', clientIden)
  66. maxThreads = 8
  67. try:
  68. maxThreads = multiprocessing.cpu_count()
  69. except:
  70. pass
  71. ##########################################################
  72. # Set up argument parser
  73. ##########################################################
  74. parser = argparse.ArgumentParser(description='Run the Framework Benchmarking test suite.',
  75. parents=[conf_parser],
  76. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  77. # SSH options
  78. parser.add_argument('-s', '--server-host', default=serverHost, help='The application server.')
  79. parser.add_argument('-c', '--client-host', default=clientHost, help='The client / load generation server.')
  80. parser.add_argument('-u', '--client-user', default=clientUser, help='The username to use for SSH to the client instance.')
  81. parser.add_argument('-i', '--client-identity-file', dest='client_identity_file', default=clientIden,
  82. help='The key to use for SSH to the client instance.')
  83. parser.add_argument('-d', '--database-host', default=databaHost,
  84. help='The database server. If not provided, defaults to the value of --client-host.')
  85. parser.add_argument('--database-user', default=databaUser,
  86. help='The username to use for SSH to the database instance. If not provided, defaults to the value of --client-user.')
  87. parser.add_argument('--database-identity-file', default=dbIdenFile, dest='database_identity_file',
  88. help='The key to use for SSH to the database instance. If not provided, defaults to the value of --client-identity-file.')
  89. parser.add_argument('-p', dest='password_prompt', action='store_true', help='Prompt for password')
  90. # Install options
  91. parser.add_argument('--install', choices=['client', 'database', 'server', 'all'], default=None,
  92. help='Runs installation script(s) before continuing on to execute the tests.')
  93. parser.add_argument('--install-error-action', choices=['abort', 'continue'], default='continue', help='action to take in case of error during installation')
  94. parser.add_argument('--install-strategy', choices=['unified', 'pertest'], default='pertest',
  95. help='''Affects `--install server`: With unified, all server software is installed into a single directory.
  96. With pertest each test gets its own installs directory, but installation takes longer''')
  97. # Test options
  98. parser.add_argument('--test', nargs='+', help='names of tests to run')
  99. parser.add_argument('--exclude', nargs='+', help='names of tests to exclude')
  100. parser.add_argument('--type', choices=['all', 'json', 'db', 'query', 'fortune', 'update', 'plaintext'], default='all', help='which type of test to run')
  101. parser.add_argument('-m', '--mode', choices=['benchmark', 'verify'], default='benchmark', help='verify mode will only start up the tests, curl the urls and shutdown')
  102. parser.add_argument('--list-tests', action='store_true', default=False, help='lists all the known tests that can run')
  103. parser.add_argument('--list-test-metadata', action='store_true', default=False, help='writes all the test metadata as a JSON file in the results directory')
  104. parser.add_argument('--name', default="ec2", help='The name to give this test. Results will be placed in a folder using this name.')
  105. parser.add_argument('--os', choices=['linux', 'windows'], default='linux', help='The operating system of the application/framework server (the one running' +
  106. 'this binary')
  107. parser.add_argument('--database-os', choices=['linux', 'windows'], default='linux', help='The operating system of the database server.')
  108. # Benchmark options
  109. parser.add_argument('--max-concurrency', default=256, help='the maximum number of HTTP connections that wrk will keep open. The query tests will run at this maximum', type=int)
  110. parser.add_argument('--max-queries', default=20, help='The maximum number of queries to run during the query test', type=int)
  111. parser.add_argument('--query-interval', default=5, type=int, help='Query tests will go from 1 query to max queries in increments of interval queries')
  112. parser.add_argument('--max-threads', default=maxThreads, help='The max number of threads to run wrk at. This should be set to the number of cores for your client system.', type=int)
  113. parser.add_argument('--duration', default=15, help='Time in seconds that each test should run for.')
  114. parser.add_argument('--starting-concurrency', default=8, type=int)
  115. parser.add_argument('--sleep', type=int, default=60, help='the amount of time to sleep after starting each test to allow the server to start up.')
  116. # Misc Options
  117. parser.add_argument('--parse', help='Parses the results of the given timestamp and merges that with the latest results')
  118. parser.add_argument('--log', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], default='ERROR', help='Set the logging level')
  119. parser.set_defaults(**defaults) # Must do this after add, or each option's default will override the configuration file default
  120. args = parser.parse_args(remaining_argv)
  121. # Set up logging
  122. numeric_level = getattr(logging, args.log.upper(), logging.ERROR)
  123. if not isinstance(numeric_level, int):
  124. raise ValueError('Invalid log level: %s' % loglevel)
  125. stdout = logging.StreamHandler()
  126. stdout.setLevel(numeric_level)
  127. stdout.setFormatter(logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s"))
  128. rootlogger = logging.getLogger()
  129. rootlogger.setLevel(logging.DEBUG) # Pass all messages to all handlers
  130. rootlogger.addHandler(stdout)
  131. logging.captureWarnings(True)
  132. # Verify and massage options
  133. if args.client_user is None:
  134. log.critical('Usernames (e.g. --client-user and --database-user) are required!')
  135. log.critical('The system will SSH into the client and the database for the install stage')
  136. log.critical('Aborting')
  137. exit(1)
  138. if args.database_user is None:
  139. args.database_user = args.client_user
  140. if args.database_host is None:
  141. args.database_host = args.client_host
  142. log.info("Configuration: %s" % str(args))
  143. benchmarker = Benchmarker(vars(args))
  144. # Run the benchmarker in the specified mode
  145. if benchmarker.list_tests:
  146. benchmarker.run_list_tests()
  147. elif benchmarker.list_test_metadata:
  148. benchmarker.run_list_test_metadata()
  149. elif benchmarker.parse != None:
  150. benchmarker.parse_timestamp()
  151. else:
  152. return benchmarker.run()
  153. # Integrate uncaught exceptions into our logging system
  154. # Note: This doesn't work if the exception happens in a
  155. # thread (e.g. inside benchmark#__run_test). This is a
  156. # python issue at http://bugs.python.org/issue1230540
  157. def handleException(excType, excValue, traceback, logger=log):
  158. logger.error("Uncaught exception", exc_info=(excType, excValue, traceback))
  159. sys.excepthook = handleException
  160. if __name__ == "__main__":
  161. sys.exit(main())