framework_test.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import importlib
  2. import os
  3. import subprocess
  4. import time
  5. import re
  6. import pprint
  7. import sys
  8. class FrameworkTest:
  9. ##########################################################################################
  10. # Class variables
  11. ##########################################################################################
  12. concurrency_template = """
  13. mysqladmin flush-hosts -uroot -psecret
  14. echo ""
  15. echo "---------------------------------------------------------"
  16. echo " Running Warmup {name}"
  17. echo " wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"
  18. echo "---------------------------------------------------------"
  19. echo ""
  20. wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}
  21. for c in {interval}
  22. do
  23. echo ""
  24. echo "---------------------------------------------------------"
  25. echo " Concurrency: $c for {name}"
  26. echo " wrk -n {runs} -c $c -t $(($c>{max_threads}?{max_threads}:$c)) http://{server_host}:{port}{url}"
  27. echo "---------------------------------------------------------"
  28. echo ""
  29. wrk -r {runs} -c "$c" -t "$(($c>{max_threads}?{max_threads}:$c))" http://{server_host}:{port}{url}
  30. done
  31. """
  32. query_template = """
  33. mysqladmin flush-hosts -uroot -psecret
  34. echo ""
  35. echo "---------------------------------------------------------"
  36. echo " Running Warmup {name}"
  37. echo " wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}2"
  38. echo "---------------------------------------------------------"
  39. echo ""
  40. wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}2
  41. for c in {interval}
  42. do
  43. echo ""
  44. echo "---------------------------------------------------------"
  45. echo " Queries: $c for {name}"
  46. echo " wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}$c"
  47. echo "---------------------------------------------------------"
  48. echo ""
  49. wrk -r {runs} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"$c"
  50. done
  51. """
  52. # The sort value is the order in which we represent all the tests. (Mainly helpful for our charts to give the underlying data)
  53. # a consistent ordering even when we add or remove tests. Each test should give a sort value in it's benchmark_config file.
  54. sort = 1000
  55. ##########################################################################################
  56. # Public Methods
  57. ##########################################################################################
  58. ############################################################
  59. # start(benchmarker)
  60. # Start the test using it's setup file
  61. ############################################################
  62. def start(self):
  63. return self.setup_module.start(self.benchmarker)
  64. ############################################################
  65. # End start
  66. ############################################################
  67. ############################################################
  68. # stop(benchmarker)
  69. # Stops the test using it's setup file
  70. ############################################################
  71. def stop(self):
  72. return self.setup_module.stop()
  73. ############################################################
  74. # End stop
  75. ############################################################
  76. ############################################################
  77. # verify_urls
  78. # Verifys each of the URLs for this test. THis will sinply
  79. # curl the URL and check for it's return status.
  80. # For each url, a flag will be set on this object for whether
  81. # or not it passed
  82. ############################################################
  83. def verify_urls(self):
  84. # JSON
  85. try:
  86. print "VERIFYING JSON (" + self.json_url + ") ..."
  87. url = self.benchmarker.generate_url(self.json_url, self.port)
  88. subprocess.check_call(["curl", "-f", url])
  89. print ""
  90. self.json_url_passed = True
  91. except (AttributeError, subprocess.CalledProcessError) as e:
  92. self.json_url_passed = False
  93. # DB
  94. try:
  95. print "VERIFYING DB (" + self.db_url + ") ..."
  96. url = self.benchmarker.generate_url(self.db_url, self.port)
  97. subprocess.check_call(["curl", "-f", url])
  98. print ""
  99. self.db_url_passed = True
  100. except (AttributeError, subprocess.CalledProcessError) as e:
  101. self.db_url_passed = False
  102. # Query
  103. try:
  104. print "VERIFYING Query (" + self.query_url + "2) ..."
  105. url = self.benchmarker.generate_url(self.query_url + "2", self.port)
  106. subprocess.check_call(["curl", "-f", url])
  107. print ""
  108. self.query_url_passed = True
  109. except (AttributeError, subprocess.CalledProcessError) as e:
  110. self.query_url_passed = False
  111. ############################################################
  112. # End verify_urls
  113. ############################################################
  114. ############################################################
  115. # benchmark
  116. # Runs the benchmark for each type of test that it implements
  117. # JSON/DB/Query.
  118. ############################################################
  119. def benchmark(self):
  120. # JSON
  121. try:
  122. if self.json_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "json"):
  123. sys.stdout.write("BENCHMARKING JSON ... ")
  124. remote_script = self.__generate_concurrency_script(self.json_url, self.port)
  125. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'json'))
  126. results = self.__parse_test('json')
  127. self.benchmarker.report_results(framework=self, test="json", success=results['succeeded'],
  128. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  129. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  130. print "Complete"
  131. except AttributeError:
  132. pass
  133. # DB
  134. try:
  135. if self.db_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "db"):
  136. sys.stdout.write("BENCHMARKING DB ... ")
  137. remote_script = self.__generate_concurrency_script(self.db_url, self.port)
  138. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'db'))
  139. results = self.__parse_test('db')
  140. self.benchmarker.report_results(framework=self, test="db", success=results['succeeded'],
  141. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  142. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  143. print "Complete"
  144. except AttributeError:
  145. pass
  146. # Query
  147. try:
  148. if self.query_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "query"):
  149. sys.stdout.write("BENCHMARKING Query ... ")
  150. remote_script = self.__generate_query_script(self.query_url, self.port)
  151. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'query'))
  152. results = self.__parse_test('query')
  153. self.benchmarker.report_results(framework=self, test="query", success=results['succeeded'],
  154. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  155. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  156. print "Complete"
  157. except AttributeError:
  158. pass
  159. ############################################################
  160. # End benchmark
  161. ############################################################
  162. ############################################################
  163. # parse_all
  164. # Method meant to be run for a given timestamp
  165. ############################################################
  166. def parse_all(self):
  167. # JSON
  168. if os.path.exists(self.benchmarker.output_file(self.name, 'json')):
  169. results = self.__parse_test('json')
  170. self.benchmarker.report_results(framework=self, test="json", success=results['succeeded'],
  171. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  172. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  173. # DB
  174. if os.path.exists(self.benchmarker.output_file(self.name, 'db')):
  175. results = self.__parse_test('db')
  176. self.benchmarker.report_results(framework=self, test="db", success=results['succeeded'],
  177. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  178. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  179. # Query
  180. if os.path.exists(self.benchmarker.output_file(self.name, 'query')):
  181. results = self.__parse_test('query')
  182. self.benchmarker.report_results(framework=self, test="query", success=results['succeeded'],
  183. failed=results['failed'], errored=results['errored'], response_2xx=results['2xx'], response_3xx=results['3xx'],
  184. response_4xx=results['4xx'], response_5xx=results['5xx'], results=results['results'], total_time=results['total_time'])
  185. ############################################################
  186. # End parse_all
  187. ############################################################
  188. ############################################################
  189. # __parse_test(test_type)
  190. ############################################################
  191. def __parse_test(self, test_type):
  192. try:
  193. results = dict()
  194. results['results'] = []
  195. results['total_time'] = 0
  196. with open(self.benchmarker.output_file(self.name, test_type)) as raw_data:
  197. found_warmup = False
  198. for line in raw_data:
  199. # wrk outputs a line with the "Requests/sec:" number for each run
  200. if "Requests/sec:" in line:
  201. # Every raw data file first has a warmup run, so we need to pass over that before we begin parsing
  202. if not found_warmup:
  203. found_warmup = True
  204. continue
  205. m = re.search("Requests/sec: ([0-9]+)", line)
  206. results['results'].append(m.group(1))
  207. if found_warmup:
  208. # search for weighttp data such as succeeded and failed.
  209. if "succeeded" in line:
  210. m = re.search("([0-9]+) succeeded", line)
  211. if m != None: results['succeeded'] = m.group(1)
  212. if "failed" in line:
  213. m = re.search("([0-9]+) failed", line)
  214. if m != None: results['failed'] = m.group(1)
  215. if "errored" in line:
  216. m = re.search("([0-9]+) errored", line)
  217. if m != None: results['errored'] = m.group(1)
  218. if "2xx" in line:
  219. m = re.search("([0-9]+) 2xx", line)
  220. if m != None: results['2xx'] = m.group(1)
  221. if "3xx" in line:
  222. m = re.search("([0-9]+) 3xx", line)
  223. if m != None: results['3xx'] = m.group(1)
  224. if "4xx" in line:
  225. m = re.search("([0-9]+) 4xx", line)
  226. if m != None: results['4xx'] = m.group(1)
  227. if "5xx" in line:
  228. m = re.search("([0-9]+) 5xx", line)
  229. if m != None: results['5xx'] = m.group(1)
  230. if "sec," in line:
  231. m = re.search("([0-9]+) sec,", line)
  232. if m != None: results['total_time'] += int(m.group(1))
  233. if "millisec" in line:
  234. m = re.search("([0-9]+) millisec", line)
  235. if m != None: results['total_time'] += ( float(m.group(1)) / 1000.0 )
  236. return results
  237. except IOError:
  238. return None
  239. ############################################################
  240. # End benchmark
  241. ############################################################
  242. ##########################################################################################
  243. # Private Methods
  244. ##########################################################################################
  245. ############################################################
  246. # __run_benchmark(script, output_file)
  247. # Runs a single benchmark using the script which is a bash
  248. # template that uses weighttp to run the test. All the results
  249. # outputed to the output_file.
  250. ############################################################
  251. def __run_benchmark(self, script, output_file):
  252. with open(output_file, 'w') as raw_file:
  253. p = subprocess.Popen(self.benchmarker.ssh_string.split(" "), stdin=subprocess.PIPE, stdout=raw_file, stderr=raw_file)
  254. p.communicate(script)
  255. ############################################################
  256. # End __run_benchmark
  257. ############################################################
  258. ############################################################
  259. # __generate_concurrency_script(url, port)
  260. # Generates the string containing the bash script that will
  261. # be run on the client to benchmark a single test. This
  262. # specifically works for the variable concurrency tests (JSON
  263. # and DB)
  264. ############################################################
  265. def __generate_concurrency_script(self, url, port):
  266. return self.concurrency_template.format(max_concurrency=self.benchmarker.max_concurrency,
  267. max_threads=self.benchmarker.max_threads, name=self.name, runs=self.benchmarker.number_of_runs,
  268. interval=" ".join("{}".format(item) for item in self.benchmarker.concurrency_levels),
  269. server_host=self.benchmarker.server_host, port=port, url=url)
  270. ############################################################
  271. # End __generate_concurrency_script
  272. ############################################################
  273. ############################################################
  274. # __generate_query_script(url, port)
  275. # Generates the string containing the bash script that will
  276. # be run on the client to benchmark a single test. This
  277. # specifically works for the variable query tests (Query)
  278. ############################################################
  279. def __generate_query_script(self, url, port):
  280. return self.query_template.format(max_concurrency=self.benchmarker.max_concurrency,
  281. max_threads=self.benchmarker.max_threads, name=self.name, runs=self.benchmarker.number_of_runs,
  282. interval=" ".join("{}".format(item) for item in self.benchmarker.query_intervals),
  283. server_host=self.benchmarker.server_host, port=port, url=url)
  284. ############################################################
  285. # End __generate_query_script
  286. ############################################################
  287. ##########################################################################################
  288. # Constructor
  289. ##########################################################################################
  290. def __init__(self, name, directory, benchmarker, args):
  291. self.name = name
  292. self.directory = directory
  293. self.benchmarker = benchmarker
  294. self.__dict__.update(args)
  295. # ensure diretory has __init__.py file so that we can use it as a pythong package
  296. if not os.path.exists(os.path.join(directory, "__init__.py")):
  297. open(os.path.join(directory, "__init__.py"), 'w').close()
  298. self.setup_module = setup_module = importlib.import_module(directory + '.' + self.setup_file)
  299. ############################################################
  300. # End __init__
  301. ############################################################
  302. ############################################################
  303. # End FrameworkTest
  304. ############################################################
  305. ##########################################################################################
  306. # Static methods
  307. ##########################################################################################
  308. ##############################################################
  309. # parse_config(config, directory, benchmarker)
  310. # parses a config file and returns a list of FrameworkTest
  311. # objects based on that config file.
  312. ##############################################################
  313. def parse_config(config, directory, benchmarker):
  314. tests = []
  315. # The config object can specify multiple tests, we neep to loop
  316. # over them and parse them out
  317. for test in config['tests']:
  318. for key, value in test.iteritems():
  319. test_name = config['framework']
  320. # if the test uses the 'defualt' keywork, then we don't
  321. # append anything to it's name. All configs should only have 1 default
  322. if key != 'default':
  323. # we need to use the key in the test_name
  324. test_name = test_name + "-" + key
  325. tests.append(FrameworkTest(test_name, directory, benchmarker, value))
  326. return tests
  327. ##############################################################
  328. # End parse_config
  329. ##############################################################