benchmarker.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. from toolset.utils.output_helper import log, FNULL
  2. from toolset.utils.docker_helper import DockerHelper
  3. from toolset.utils.time_logger import TimeLogger
  4. from toolset.utils.metadata import Metadata
  5. from toolset.utils.results import Results
  6. from toolset.utils.audit import Audit
  7. import os
  8. import subprocess
  9. import traceback
  10. import sys
  11. import time
  12. import shlex
  13. from pprint import pprint
  14. from colorama import Fore
  15. import numbers
  16. class Benchmarker:
  17. def __init__(self, config):
  18. '''
  19. Initialize the benchmarker.
  20. '''
  21. self.config = config
  22. self.time_logger = TimeLogger()
  23. self.metadata = Metadata(self)
  24. self.audit = Audit(self)
  25. # a list of all tests for this run
  26. self.tests = self.metadata.tests_to_run()
  27. self.results = Results(self)
  28. self.docker_helper = DockerHelper(self)
  29. self.last_test = False
  30. ##########################################################################################
  31. # Public methods
  32. ##########################################################################################
  33. def run(self):
  34. '''
  35. This process involves setting up the client/server machines
  36. with any necessary change. Then going through each test,
  37. running their docker build and run, verifying the URLs, and
  38. running benchmarks against them.
  39. '''
  40. # Generate metadata
  41. self.metadata.list_test_metadata()
  42. any_failed = False
  43. # Run tests
  44. log("Running Tests...", border='=')
  45. # build wrk and all databases needed for current run
  46. self.docker_helper.build_wrk()
  47. self.docker_helper.build_databases()
  48. with open(os.path.join(self.results.directory, 'benchmark.log'),
  49. 'w') as benchmark_log:
  50. for test in self.tests:
  51. if self.tests.index(test) + 1 == len(self.tests):
  52. self.last_test = True
  53. log("Running Test: %s" % test.name, border='-')
  54. with self.config.quiet_out.enable():
  55. if not self.__run_test(test, benchmark_log):
  56. any_failed = True
  57. # Load intermediate result from child process
  58. self.results.load()
  59. # Parse results
  60. if self.config.mode == "benchmark":
  61. log("Parsing Results ...", border='=')
  62. self.results.parse(self.tests)
  63. self.results.set_completion_time()
  64. self.results.upload()
  65. self.results.finish()
  66. return any_failed
  67. def stop(self, signal=None, frame=None):
  68. log("Shutting down (may take a moment)")
  69. self.docker_helper.stop()
  70. sys.exit(0)
  71. ##########################################################################################
  72. # Private methods
  73. ##########################################################################################
  74. def __exit_test(self, success, prefix, file, message=None):
  75. if message:
  76. log(message,
  77. prefix=prefix,
  78. file=file,
  79. color=Fore.RED if success else '')
  80. self.time_logger.log_test_end(log_prefix=prefix, file=file)
  81. if not self.last_test:
  82. # Sleep for 60 seconds to ensure all host connects are closed
  83. log("Clean up: Sleep 60 seconds...", prefix=prefix, file=file)
  84. time.sleep(60)
  85. return success
  86. def __run_test(self, test, benchmark_log):
  87. '''
  88. Runs the given test, verifies that the webapp is accepting requests,
  89. optionally benchmarks the webapp, and ultimately stops all services
  90. started for this test.
  91. '''
  92. log_prefix = "%s: " % test.name
  93. # Start timing the total test duration
  94. self.time_logger.mark_test_start()
  95. if self.config.mode == "benchmark":
  96. log("Benchmarking %s" % test.name,
  97. file=benchmark_log,
  98. border='-')
  99. # If the test is in the excludes list, we skip it
  100. if self.config.exclude and test.name in self.config.exclude:
  101. message = "Test {name} has been added to the excludes list. Skipping.".format(
  102. name=test.name)
  103. self.results.write_intermediate(test.name, message)
  104. self.results.upload()
  105. return self.__exit_test(
  106. success=False,
  107. message=message,
  108. prefix=log_prefix,
  109. file=benchmark_log)
  110. database_container = None
  111. try:
  112. # Start database container
  113. if test.database.lower() != "none":
  114. self.time_logger.mark_starting_database()
  115. database_container = self.docker_helper.start_database(
  116. test.database.lower())
  117. if database_container is None:
  118. message = "ERROR: Problem building/running database container"
  119. self.results.write_intermediate(test.name, message)
  120. self.results.upload()
  121. return self.__exit_test(
  122. success=False,
  123. message=message,
  124. prefix=log_prefix,
  125. file=benchmark_log)
  126. self.time_logger.mark_started_database()
  127. # Start webapp
  128. container = test.start()
  129. self.time_logger.mark_test_starting()
  130. if container is None:
  131. message = "ERROR: Problem starting {name}".format(
  132. name=test.name)
  133. self.results.write_intermediate(test.name, message)
  134. self.results.upload()
  135. return self.__exit_test(
  136. success=False,
  137. message=message,
  138. prefix=log_prefix,
  139. file=benchmark_log)
  140. max_time = time.time() + 60
  141. while True:
  142. accepting_requests = test.is_accepting_requests()
  143. if accepting_requests \
  144. or time.time() >= max_time \
  145. or not self.docker_helper.server_container_exists(container.id):
  146. break
  147. time.sleep(1)
  148. if hasattr(test, 'wait_before_sending_requests') and isinstance(test.wait_before_sending_requests, numbers.Integral) and test.wait_before_sending_requests > 0:
  149. time.sleep(test.wait_before_sending_requests)
  150. if not accepting_requests:
  151. message = "ERROR: Framework is not accepting requests from client machine"
  152. self.results.write_intermediate(test.name, message)
  153. self.results.upload()
  154. return self.__exit_test(
  155. success=False,
  156. message=message,
  157. prefix=log_prefix,
  158. file=benchmark_log)
  159. self.time_logger.mark_test_accepting_requests()
  160. # Debug mode blocks execution here until ctrl+c
  161. if self.config.mode == "debug":
  162. msg = "Entering debug mode. Server http://localhost:%s has started. CTRL-c to stop.\r\n" % test.port
  163. msg = msg + "From outside vagrant: http://localhost:%s" % (int(test.port) + 20000)
  164. log(msg,
  165. prefix=log_prefix,
  166. file=benchmark_log,
  167. color=Fore.YELLOW)
  168. while True:
  169. time.sleep(1)
  170. # Verify URLs and audit
  171. log("Verifying framework URLs", prefix=log_prefix)
  172. self.time_logger.mark_verify_start()
  173. passed_verify = test.verify_urls()
  174. self.audit.audit_test_dir(test.directory)
  175. # Benchmark this test
  176. if self.config.mode == "benchmark":
  177. self.time_logger.mark_benchmarking_start()
  178. self.__benchmark(test, benchmark_log)
  179. self.time_logger.log_benchmarking_end(
  180. log_prefix=log_prefix, file=benchmark_log)
  181. # Log test timing stats
  182. self.time_logger.log_build_flush(benchmark_log)
  183. self.time_logger.log_database_start_time(log_prefix, benchmark_log)
  184. self.time_logger.log_test_accepting_requests(
  185. log_prefix, benchmark_log)
  186. self.time_logger.log_verify_end(log_prefix, benchmark_log)
  187. # Save results thus far into the latest results directory
  188. self.results.write_intermediate(test.name,
  189. time.strftime(
  190. "%Y%m%d%H%M%S",
  191. time.localtime()))
  192. # Upload the results thus far to another server (optional)
  193. self.results.upload()
  194. if self.config.mode == "verify" and not passed_verify:
  195. return self.__exit_test(
  196. success=False,
  197. message="Failed verify!",
  198. prefix=log_prefix,
  199. file=benchmark_log)
  200. except Exception as e:
  201. tb = traceback.format_exc()
  202. self.results.write_intermediate(test.name,
  203. "error during test: " + str(e))
  204. self.results.upload()
  205. log(tb, prefix=log_prefix, file=benchmark_log)
  206. return self.__exit_test(
  207. success=False,
  208. message="Error during test: %s" % test.name,
  209. prefix=log_prefix,
  210. file=benchmark_log)
  211. finally:
  212. self.docker_helper.stop()
  213. return self.__exit_test(
  214. success=True, prefix=log_prefix, file=benchmark_log)
  215. def __benchmark(self, framework_test, benchmark_log):
  216. '''
  217. Runs the benchmark for each type of test that it implements
  218. '''
  219. def benchmark_type(test_type):
  220. log("BENCHMARKING %s ... " % test_type.upper(), file=benchmark_log)
  221. test = framework_test.runTests[test_type]
  222. raw_file = self.results.get_raw_file(framework_test.name,
  223. test_type)
  224. if not os.path.exists(raw_file):
  225. # Open to create the empty file
  226. with open(raw_file, 'w'):
  227. pass
  228. if not test.failed:
  229. # Begin resource usage metrics collection
  230. self.__begin_logging(framework_test, test_type)
  231. script = self.config.types[test_type].get_script_name()
  232. script_variables = self.config.types[
  233. test_type].get_script_variables(
  234. test.name, "http://%s:%s%s" % (self.config.server_host,
  235. framework_test.port,
  236. test.get_url()))
  237. self.docker_helper.benchmark(script, script_variables,
  238. raw_file)
  239. # End resource usage metrics collection
  240. self.__end_logging()
  241. results = self.results.parse_test(framework_test, test_type)
  242. log("Benchmark results:", file=benchmark_log)
  243. # TODO move into log somehow
  244. pprint(results)
  245. self.results.report_benchmark_results(framework_test, test_type,
  246. results['results'])
  247. log("Complete", file=benchmark_log)
  248. for test_type in framework_test.runTests:
  249. benchmark_type(test_type)
  250. def __begin_logging(self, framework_test, test_type):
  251. '''
  252. Starts a thread to monitor the resource usage, to be synced with the
  253. client's time.
  254. TODO: MySQL and InnoDB are possible. Figure out how to implement them.
  255. '''
  256. output_file = "{file_name}".format(
  257. file_name=self.results.get_stats_file(framework_test.name,
  258. test_type))
  259. dstat_string = "dstat -Tafilmprs --aio --fs --ipc --lock --socket --tcp \
  260. --raw --udp --unix --vm --disk-util \
  261. --rpc --rpcd --output {output_file}".format(
  262. output_file=output_file)
  263. cmd = shlex.split(dstat_string)
  264. self.subprocess_handle = subprocess.Popen(
  265. cmd, stdout=FNULL, stderr=subprocess.STDOUT)
  266. def __end_logging(self):
  267. '''
  268. Stops the logger thread and blocks until shutdown is complete.
  269. '''
  270. self.subprocess_handle.terminate()
  271. self.subprocess_handle.communicate()