framework_test.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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 Primer {name}"
  17. echo " wrk -d 60 -c 8 -t 8 http://{server_host}:{port}{url}"
  18. echo "---------------------------------------------------------"
  19. echo ""
  20. wrk -d 5 -c 8 -t 8 http://{server_host}:{port}{url}
  21. sleep 5
  22. echo ""
  23. echo "---------------------------------------------------------"
  24. echo " Running Warmup {name}"
  25. echo " wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"
  26. echo "---------------------------------------------------------"
  27. echo ""
  28. wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}
  29. sleep 5
  30. for c in {interval}
  31. do
  32. echo ""
  33. echo "---------------------------------------------------------"
  34. echo " Concurrency: $c for {name}"
  35. echo " wrk -d {duration} -c $c -t $(($c>{max_threads}?{max_threads}:$c)) http://{server_host}:{port}{url}"
  36. echo "---------------------------------------------------------"
  37. echo ""
  38. wrk -d {duration} -c "$c" -t "$(($c>{max_threads}?{max_threads}:$c))" http://{server_host}:{port}{url}
  39. sleep 2
  40. done
  41. """
  42. query_template = """
  43. mysqladmin flush-hosts -uroot -psecret
  44. echo ""
  45. echo "---------------------------------------------------------"
  46. echo " Running Primer {name}"
  47. echo " wrk -d 5 -c 8 -t 8 http://{server_host}:{port}{url}2"
  48. echo "---------------------------------------------------------"
  49. echo ""
  50. wrk -d 5 -c 8 -t 8 http://{server_host}:{port}{url}2
  51. sleep 5
  52. echo ""
  53. echo "---------------------------------------------------------"
  54. echo " Running Warmup {name}"
  55. echo " wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}2"
  56. echo "---------------------------------------------------------"
  57. echo ""
  58. wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}2
  59. sleep 5
  60. for c in {interval}
  61. do
  62. echo ""
  63. echo "---------------------------------------------------------"
  64. echo " Queries: $c for {name}"
  65. echo " wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}$c"
  66. echo "---------------------------------------------------------"
  67. echo ""
  68. wrk -d {duration} -c {max_concurrency} -t {max_threads} http://{server_host}:{port}{url}"$c"
  69. sleep 2
  70. done
  71. """
  72. # The sort value is the order in which we represent all the tests. (Mainly helpful for our charts to give the underlying data)
  73. # a consistent ordering even when we add or remove tests. Each test should give a sort value in it's benchmark_config file.
  74. sort = 1000
  75. os = 'linux'
  76. ##########################################################################################
  77. # Public Methods
  78. ##########################################################################################
  79. ############################################################
  80. # start(benchmarker)
  81. # Start the test using it's setup file
  82. ############################################################
  83. def start(self):
  84. return self.setup_module.start(self.benchmarker)
  85. ############################################################
  86. # End start
  87. ############################################################
  88. ############################################################
  89. # stop(benchmarker)
  90. # Stops the test using it's setup file
  91. ############################################################
  92. def stop(self):
  93. return self.setup_module.stop()
  94. ############################################################
  95. # End stop
  96. ############################################################
  97. ############################################################
  98. # verify_urls
  99. # Verifys each of the URLs for this test. THis will sinply
  100. # curl the URL and check for it's return status.
  101. # For each url, a flag will be set on this object for whether
  102. # or not it passed
  103. ############################################################
  104. def verify_urls(self):
  105. # JSON
  106. try:
  107. print "VERIFYING JSON (" + self.json_url + ") ..."
  108. url = self.benchmarker.generate_url(self.json_url, self.port)
  109. subprocess.check_call(["curl", "-f", url])
  110. print ""
  111. self.json_url_passed = True
  112. except (AttributeError, subprocess.CalledProcessError) as e:
  113. self.json_url_passed = False
  114. # DB
  115. try:
  116. print "VERIFYING DB (" + self.db_url + ") ..."
  117. url = self.benchmarker.generate_url(self.db_url, self.port)
  118. subprocess.check_call(["curl", "-f", url])
  119. print ""
  120. self.db_url_passed = True
  121. except (AttributeError, subprocess.CalledProcessError) as e:
  122. self.db_url_passed = False
  123. # Query
  124. try:
  125. print "VERIFYING Query (" + self.query_url + "2) ..."
  126. url = self.benchmarker.generate_url(self.query_url + "2", self.port)
  127. subprocess.check_call(["curl", "-f", url])
  128. print ""
  129. self.query_url_passed = True
  130. except (AttributeError, subprocess.CalledProcessError) as e:
  131. self.query_url_passed = False
  132. # Fortune
  133. try:
  134. print "VERIFYING Fortune (" + self.fortune_url + ") ..."
  135. url = self.benchmarker.generate_url(self.fortune_url, self.port)
  136. subprocess.check_call(["curl", "-f", url])
  137. print ""
  138. self.fortune_url_passed = True
  139. except (AttributeError, subprocess.CalledProcessError) as e:
  140. self.fortune_url_passed = False
  141. # Update
  142. try:
  143. print "VERIFYING Update (" + self.update_url + "2) ..."
  144. url = self.benchmarker.generate_url(self.update_url + "2", self.port)
  145. subprocess.check_call(["curl", "-f", url])
  146. print ""
  147. self.update_url_passed = True
  148. except (AttributeError, subprocess.CalledProcessError) as e:
  149. self.update_url_passed = False
  150. ############################################################
  151. # End verify_urls
  152. ############################################################
  153. ############################################################
  154. # contains_type(type)
  155. # true if this test contains an implementation of the given
  156. # test type (json, db, etc.)
  157. ############################################################
  158. def contains_type(self, type):
  159. try:
  160. if type == 'json' and self.json_url != None:
  161. return True
  162. if type == 'db' and self.db_url != None:
  163. return True
  164. if type == 'query' and self.query_url != None:
  165. return True
  166. if type == 'fortune' and self.fortune_url != None:
  167. return True
  168. if type == 'update' and self.update_url != None:
  169. return True
  170. except AttributeError:
  171. pass
  172. return False
  173. ############################################################
  174. # End stop
  175. ############################################################
  176. ############################################################
  177. # benchmark
  178. # Runs the benchmark for each type of test that it implements
  179. # JSON/DB/Query.
  180. ############################################################
  181. def benchmark(self):
  182. # JSON
  183. try:
  184. if self.json_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "json"):
  185. sys.stdout.write("BENCHMARKING JSON ... ")
  186. sys.stdout.flush()
  187. remote_script = self.__generate_concurrency_script(self.json_url, self.port)
  188. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'json'))
  189. results = self.__parse_test('json')
  190. self.benchmarker.report_results(framework=self, test="json", results=results['results'])
  191. print "Complete"
  192. except AttributeError:
  193. pass
  194. # DB
  195. try:
  196. if self.db_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "db"):
  197. sys.stdout.write("BENCHMARKING DB ... ")
  198. sys.stdout.flush()
  199. remote_script = self.__generate_concurrency_script(self.db_url, self.port)
  200. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'db'))
  201. results = self.__parse_test('db')
  202. self.benchmarker.report_results(framework=self, test="db", results=results['results'])
  203. print "Complete"
  204. except AttributeError:
  205. pass
  206. # Query
  207. try:
  208. if self.query_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "query"):
  209. sys.stdout.write("BENCHMARKING Query ... ")
  210. sys.stdout.flush()
  211. remote_script = self.__generate_query_script(self.query_url, self.port)
  212. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'query'))
  213. results = self.__parse_test('query')
  214. self.benchmarker.report_results(framework=self, test="query", results=results['results'])
  215. print "Complete"
  216. except AttributeError:
  217. pass
  218. # fortune
  219. try:
  220. if self.fortune_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "fortune"):
  221. sys.stdout.write("BENCHMARKING Fortune ... ")
  222. sys.stdout.flush()
  223. remote_script = self.__generate_concurrency_script(self.fortune_url, self.port)
  224. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'fortune'))
  225. results = self.__parse_test('fortune')
  226. self.benchmarker.report_results(framework=self, test="fortune", results=results['results'])
  227. print "Complete"
  228. except AttributeError:
  229. pass
  230. # update
  231. try:
  232. if self.update_url_passed and (self.benchmarker.type == "all" or self.benchmarker.type == "update"):
  233. sys.stdout.write("BENCHMARKING Update ... ")
  234. sys.stdout.flush()
  235. remote_script = self.__generate_query_script(self.update_url, self.port)
  236. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, 'update'))
  237. results = self.__parse_test('update')
  238. self.benchmarker.report_results(framework=self, test="update", results=results['results'])
  239. print "Complete"
  240. except AttributeError:
  241. pass
  242. ############################################################
  243. # End benchmark
  244. ############################################################
  245. ############################################################
  246. # parse_all
  247. # Method meant to be run for a given timestamp
  248. ############################################################
  249. def parse_all(self):
  250. # JSON
  251. if os.path.exists(self.benchmarker.output_file(self.name, 'json')):
  252. results = self.__parse_test('json')
  253. self.benchmarker.report_results(framework=self, test="json", results=results['results'])
  254. # DB
  255. if os.path.exists(self.benchmarker.output_file(self.name, 'db')):
  256. results = self.__parse_test('db')
  257. self.benchmarker.report_results(framework=self, test="db", results=results['results'])
  258. # Query
  259. if os.path.exists(self.benchmarker.output_file(self.name, 'query')):
  260. results = self.__parse_test('query')
  261. self.benchmarker.report_results(framework=self, test="query", results=results['results'])
  262. # Fortune
  263. if os.path.exists(self.benchmarker.output_file(self.name, 'fortune')):
  264. results = self.__parse_test('fortune')
  265. self.benchmarker.report_results(framework=self, test="fortune", results=results['results'])
  266. # Update
  267. if os.path.exists(self.benchmarker.output_file(self.name, 'update')):
  268. results = self.__parse_test('update')
  269. self.benchmarker.report_results(framework=self, test="update", results=results['results'])
  270. ############################################################
  271. # End parse_all
  272. ############################################################
  273. ############################################################
  274. # __parse_test(test_type)
  275. ############################################################
  276. def __parse_test(self, test_type):
  277. try:
  278. results = dict()
  279. results['results'] = []
  280. with open(self.benchmarker.output_file(self.name, test_type)) as raw_data:
  281. is_warmup = True
  282. rawData = None
  283. for line in raw_data:
  284. if "Queries:" in line or "Concurrency:" in line:
  285. is_warmup = False
  286. rawData = None
  287. continue
  288. if "Warmup" in line or "Primer" in line:
  289. is_warmup = True
  290. continue
  291. if not is_warmup:
  292. if rawData == None:
  293. rawData = dict()
  294. results['results'].append(rawData)
  295. #if "Requests/sec:" in line:
  296. # m = re.search("Requests/sec:\s+([0-9]+)", line)
  297. # rawData['reportedResults'] = m.group(1)
  298. # search for weighttp data such as succeeded and failed.
  299. if "Latency" in line:
  300. m = re.findall("([0-9]+\.*[0-9]*[us|ms|s|m|%]+)", line)
  301. if len(m) == 4:
  302. rawData['latencyAvg'] = m[0]
  303. rawData['latencyStdev'] = m[1]
  304. rawData['latencyMax'] = m[2]
  305. # rawData['latencyStdevPercent'] = m[3]
  306. #if "Req/Sec" in line:
  307. # m = re.findall("([0-9]+\.*[0-9]*[k|%]*)", line)
  308. # if len(m) == 4:
  309. # rawData['requestsAvg'] = m[0]
  310. # rawData['requestsStdev'] = m[1]
  311. # rawData['requestsMax'] = m[2]
  312. # rawData['requestsStdevPercent'] = m[3]
  313. #if "requests in" in line:
  314. # m = re.search("requests in ([0-9]+\.*[0-9]*[ms|s|m|h]+)", line)
  315. # if m != None:
  316. # # parse out the raw time, which may be in minutes or seconds
  317. # raw_time = m.group(1)
  318. # if "ms" in raw_time:
  319. # rawData['total_time'] = float(raw_time[:len(raw_time)-2]) / 1000.0
  320. # elif "s" in raw_time:
  321. # rawData['total_time'] = float(raw_time[:len(raw_time)-1])
  322. # elif "m" in raw_time:
  323. # rawData['total_time'] = float(raw_time[:len(raw_time)-1]) * 60.0
  324. # elif "h" in raw_time:
  325. # rawData['total_time'] = float(raw_time[:len(raw_time)-1]) * 3600.0
  326. if "requests in" in line:
  327. m = re.search("([0-9]+) requests in", line)
  328. if m != None:
  329. rawData['totalRequests'] = int(m.group(1))
  330. if "Socket errors" in line:
  331. if "connect" in line:
  332. m = re.search("connect ([0-9]+)", line)
  333. rawData['connect'] = int(m.group(1))
  334. if "read" in line:
  335. m = re.search("read ([0-9]+)", line)
  336. rawData['read'] = int(m.group(1))
  337. if "write" in line:
  338. m = re.search("write ([0-9]+)", line)
  339. rawData['write'] = int(m.group(1))
  340. if "timeout" in line:
  341. m = re.search("timeout ([0-9]+)", line)
  342. rawData['timeout'] = int(m.group(1))
  343. if "Non-2xx" in line:
  344. m = re.search("Non-2xx or 3xx responses: ([0-9]+)", line)
  345. if m != None:
  346. rawData['5xx'] = int(m.group(1))
  347. return results
  348. except IOError:
  349. return None
  350. ############################################################
  351. # End benchmark
  352. ############################################################
  353. ##########################################################################################
  354. # Private Methods
  355. ##########################################################################################
  356. ############################################################
  357. # __run_benchmark(script, output_file)
  358. # Runs a single benchmark using the script which is a bash
  359. # template that uses weighttp to run the test. All the results
  360. # outputed to the output_file.
  361. ############################################################
  362. def __run_benchmark(self, script, output_file):
  363. with open(output_file, 'w') as raw_file:
  364. p = subprocess.Popen(self.benchmarker.ssh_string.split(" "), stdin=subprocess.PIPE, stdout=raw_file, stderr=raw_file)
  365. p.communicate(script)
  366. ############################################################
  367. # End __run_benchmark
  368. ############################################################
  369. ############################################################
  370. # __generate_concurrency_script(url, port)
  371. # Generates the string containing the bash script that will
  372. # be run on the client to benchmark a single test. This
  373. # specifically works for the variable concurrency tests (JSON
  374. # and DB)
  375. ############################################################
  376. def __generate_concurrency_script(self, url, port):
  377. return self.concurrency_template.format(max_concurrency=self.benchmarker.max_concurrency,
  378. max_threads=self.benchmarker.max_threads, name=self.name, duration=self.benchmarker.duration,
  379. interval=" ".join("{}".format(item) for item in self.benchmarker.concurrency_levels),
  380. server_host=self.benchmarker.server_host, port=port, url=url)
  381. ############################################################
  382. # End __generate_concurrency_script
  383. ############################################################
  384. ############################################################
  385. # __generate_query_script(url, port)
  386. # Generates the string containing the bash script that will
  387. # be run on the client to benchmark a single test. This
  388. # specifically works for the variable query tests (Query)
  389. ############################################################
  390. def __generate_query_script(self, url, port):
  391. return self.query_template.format(max_concurrency=self.benchmarker.max_concurrency,
  392. max_threads=self.benchmarker.max_threads, name=self.name, duration=self.benchmarker.duration,
  393. interval=" ".join("{}".format(item) for item in self.benchmarker.query_intervals),
  394. server_host=self.benchmarker.server_host, port=port, url=url)
  395. ############################################################
  396. # End __generate_query_script
  397. ############################################################
  398. ##########################################################################################
  399. # Constructor
  400. ##########################################################################################
  401. def __init__(self, name, directory, benchmarker, args):
  402. self.name = name
  403. self.directory = directory
  404. self.benchmarker = benchmarker
  405. self.__dict__.update(args)
  406. # ensure diretory has __init__.py file so that we can use it as a pythong package
  407. if not os.path.exists(os.path.join(directory, "__init__.py")):
  408. open(os.path.join(directory, "__init__.py"), 'w').close()
  409. self.setup_module = setup_module = importlib.import_module(directory + '.' + self.setup_file)
  410. ############################################################
  411. # End __init__
  412. ############################################################
  413. ############################################################
  414. # End FrameworkTest
  415. ############################################################
  416. ##########################################################################################
  417. # Static methods
  418. ##########################################################################################
  419. ##############################################################
  420. # parse_config(config, directory, benchmarker)
  421. # parses a config file and returns a list of FrameworkTest
  422. # objects based on that config file.
  423. ##############################################################
  424. def parse_config(config, directory, benchmarker):
  425. tests = []
  426. # The config object can specify multiple tests, we neep to loop
  427. # over them and parse them out
  428. for test in config['tests']:
  429. for key, value in test.iteritems():
  430. test_name = config['framework']
  431. # if the test uses the 'defualt' keywork, then we don't
  432. # append anything to it's name. All configs should only have 1 default
  433. if key != 'default':
  434. # we need to use the key in the test_name
  435. test_name = test_name + "-" + key
  436. tests.append(FrameworkTest(test_name, directory, benchmarker, value))
  437. return tests
  438. ##############################################################
  439. # End parse_config
  440. ##############################################################