framework_test.py 21 KB

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