framework_test.py 16 KB

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