run-tests.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env python
  2. import argparse
  3. import ConfigParser
  4. import socket
  5. import sys
  6. import time
  7. import os
  8. import platform
  9. import multiprocessing
  10. import itertools
  11. import copy
  12. from benchmark.benchmarker import Benchmarker
  13. from setup.linux.unbuffered import Unbuffered
  14. from setup.linux import setup_util
  15. from ast import literal_eval
  16. # Enable cross-platform colored output
  17. from colorama import init
  18. init()
  19. class StoreSeqAction(argparse.Action):
  20. '''Helper class for parsing a sequence from the command line'''
  21. def __init__(self, option_strings, dest, nargs=None, **kwargs):
  22. super(StoreSeqAction, self).__init__(option_strings, dest, type=str, **kwargs)
  23. def __call__(self, parser, namespace, values, option_string=None):
  24. setattr(namespace, self.dest, self.parse_seq(values))
  25. def parse_seq(self, argument):
  26. result = argument.split(',')
  27. sequences = [x for x in result if ":" in x]
  28. for sequence in sequences:
  29. try:
  30. (start,step,end) = sequence.split(':')
  31. except ValueError:
  32. print(" Invalid: {!s}".format(sequence))
  33. print(" Requires start:step:end, e.g. 1:2:10")
  34. raise
  35. result.remove(sequence)
  36. result = result + range(int(start), int(end), int(step))
  37. return [abs(int(item)) for item in result]
  38. ###################################################################################################
  39. # Main
  40. ###################################################################################################
  41. def main(argv=None):
  42. ''' Runs the program. There are three ways to pass arguments
  43. 1) environment variables TFB_*
  44. 2) configuration file benchmark.cfg
  45. 3) command line flags
  46. In terms of precedence, 3 > 2 > 1, so config file trumps environment variables
  47. but command line flags have the final say
  48. '''
  49. # Do argv default this way, as doing it in the functional declaration sets it at compile time
  50. if argv is None:
  51. argv = sys.argv
  52. # Enable unbuffered output so messages will appear in the proper order with subprocess output.
  53. sys.stdout=Unbuffered(sys.stdout)
  54. # Update python environment
  55. # 1) Ensure the current directory (which should be the benchmark home directory) is in the path so that the tests can be imported.
  56. sys.path.append('.')
  57. # 2) Ensure toolset/setup/linux is in the path so that the tests can "import setup_util".
  58. sys.path.append('toolset/setup/linux')
  59. # Update environment for shell scripts
  60. os.environ['FWROOT'] = setup_util.get_fwroot()
  61. os.environ['IROOT'] = os.environ['FWROOT'] + '/installs'
  62. # 'Ubuntu', '14.04', 'trusty' respectively
  63. os.environ['TFB_DISTRIB_ID'], os.environ['TFB_DISTRIB_RELEASE'], os.environ['TFB_DISTRIB_CODENAME'] = platform.linux_distribution()
  64. print("FWROOT is {!s}.".format(os.environ['FWROOT']))
  65. conf_parser = argparse.ArgumentParser(
  66. description=__doc__,
  67. formatter_class=argparse.RawDescriptionHelpFormatter,
  68. add_help=False)
  69. conf_parser.add_argument(
  70. '--conf_file', default='benchmark.cfg', metavar='FILE',
  71. help='Optional configuration file to provide argument defaults. All config options can be overridden using the command line.')
  72. args, remaining_argv = conf_parser.parse_known_args()
  73. defaults = {}
  74. try:
  75. if not os.path.exists(os.path.join(os.environ['FWROOT'], args.conf_file)) and not os.path.exists(os.path.join(os.environ['FWROOT'] + 'benchmark.cfg')):
  76. print("No config file found. Aborting!")
  77. exit(1)
  78. with open (os.path.join(os.environ['FWROOT'], args.conf_file)):
  79. config = ConfigParser.SafeConfigParser()
  80. config.read([os.path.join(os.environ['FWROOT'], args.conf_file)])
  81. defaults.update(dict(config.items("Defaults")))
  82. # Convert strings into proper python types
  83. for k, v in defaults.iteritems():
  84. try:
  85. defaults[k] = literal_eval(v)
  86. except Exception:
  87. pass
  88. except IOError:
  89. print("Configuration file not found!")
  90. exit(1)
  91. ##########################################################
  92. # Set up default values
  93. ##########################################################
  94. # Verify and massage options
  95. if defaults['client_user'] is None or defaults['client_host'] is None:
  96. print("client_user and client_host are required!")
  97. print("Please check your configuration file.")
  98. print("Aborting!")
  99. exit(1)
  100. if defaults['database_user'] is None:
  101. defaults['database_user'] = defaults['client_user']
  102. if defaults['database_host'] is None:
  103. defaults['database_host'] = defaults['client_host']
  104. if defaults['server_host'] is None:
  105. defaults['server_host'] = defaults['client_host']
  106. maxThreads = 8
  107. try:
  108. maxThreads = multiprocessing.cpu_count()
  109. except Exception:
  110. pass
  111. ##########################################################
  112. # Set up argument parser
  113. ##########################################################
  114. parser = argparse.ArgumentParser(description="Install or run the Framework Benchmarks test suite.",
  115. parents=[conf_parser],
  116. formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  117. epilog='''If an argument includes (type int-sequence), then it accepts integer lists in multiple forms.
  118. Using a single number e.g. 5 will create a list [5]. Using commas will create a list containing those
  119. values e.g. 1,3,6 creates [1, 3, 6]. Using three colon-separated numbers of start:step:end will create a
  120. list, using the semantics of python's range function, e.g. 1:3:15 creates [1, 4, 7, 10, 13] while
  121. 0:1:5 creates [0, 1, 2, 3, 4]
  122. ''')
  123. # Install options
  124. parser.add_argument('--clean', action='store_true', default=False, help='Removes the results directory')
  125. parser.add_argument('--clean-all', action='store_true', dest='clean_all', default=False, help='Removes the results and installs directories')
  126. # Test options
  127. parser.add_argument('--test', nargs='+', help='names of tests to run')
  128. parser.add_argument('--test-dir', nargs='+', dest='test_dir', help='name of framework directory containing all tests to run')
  129. parser.add_argument('--exclude', nargs='+', help='names of tests to exclude')
  130. parser.add_argument('--type', choices=['all', 'json', 'db', 'query', 'fortune', 'update', 'plaintext'], default='all', help='which type of test to run')
  131. parser.add_argument('-m', '--mode', choices=['benchmark', 'verify'], default='benchmark', help='verify mode will only start up the tests, curl the urls and shutdown')
  132. parser.add_argument('--list-tests', action='store_true', default=False, help='lists all the known tests that can run')
  133. # Benchmark options
  134. parser.add_argument('--concurrency-levels', default=[8, 16, 32, 64, 128, 256], help='Runs wrk benchmarker with different concurrency value (type int-sequence)', action=StoreSeqAction)
  135. parser.add_argument('--query-levels', default=[1, 5,10,15,20], help='Database queries requested per HTTP connection, used during query test (type int-sequence)', action=StoreSeqAction)
  136. parser.add_argument('--threads', default=maxThreads, help='Run wrk benchmarker with this many threads. This should probably be the number of cores for your client system', type=int)
  137. parser.add_argument('--duration', default=15, help='Time in seconds that each test should run for.')
  138. 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.')
  139. # Misc Options
  140. parser.add_argument('--results-name', help='Gives a name to this set of results, formatted as a date', default='(unspecified, datetime = %Y-%m-%d %H:%M:%S)')
  141. parser.add_argument('--results-environment', help='Describes the environment in which these results were gathered', default='(unspecified, hostname = %s)' % socket.gethostname())
  142. parser.add_argument('--parse', help='Parses the results of the given timestamp and merges that with the latest results')
  143. parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Causes the configuration to print before any other commands are executed.')
  144. parser.add_argument('--clear-tmp', action='store_true', default=False, help='Clears files written to /tmp after each framework\'s tests complete.')
  145. parser.set_defaults(**defaults) # Must do this after add, or each option's default will override the configuration file default
  146. args = parser.parse_args(remaining_argv)
  147. benchmarker = Benchmarker(vars(args))
  148. # Run the benchmarker in the specified mode
  149. # Do not use benchmarker variables for these checks,
  150. # they are either str or bool based on the python version
  151. if args.list_tests:
  152. benchmarker.run_list_tests()
  153. elif args.parse != None:
  154. benchmarker.parse_timestamp()
  155. else:
  156. return benchmarker.run()
  157. if __name__ == "__main__":
  158. sys.exit(main())