framework_test.py 24 KB

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