framework_test.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. from benchmark.fortune_html_parser import FortuneHTMLParser
  2. import importlib
  3. import os
  4. import subprocess
  5. import time
  6. import re
  7. import pprint
  8. import sys
  9. import traceback
  10. import json
  11. class FrameworkTest:
  12. ##########################################################################################
  13. # Class variables
  14. ##########################################################################################
  15. headers_template = "-H 'Host: localhost' -H '{accept}' -H 'Connection: keep-alive'"
  16. 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'"
  17. accept_json = "Accept: application/json,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7"
  18. accept_html = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  19. accept_plaintext = "Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7"
  20. concurrency_template = """
  21. echo ""
  22. echo "---------------------------------------------------------"
  23. echo " Running Primer {name}"
  24. echo " {wrk} {headers} -d 5 -c 8 -t 8 \"http://{server_host}:{port}{url}\""
  25. echo "---------------------------------------------------------"
  26. echo ""
  27. {wrk} {headers} -d 5 -c 8 -t 8 "http://{server_host}:{port}{url}"
  28. sleep 5
  29. echo ""
  30. echo "---------------------------------------------------------"
  31. echo " Running Warmup {name}"
  32. echo " {wrk} {headers} -d {duration} -c {max_concurrency} -t {max_threads} \"http://{server_host}:{port}{url}\""
  33. echo "---------------------------------------------------------"
  34. echo ""
  35. {wrk} {headers} -d {duration} -c {max_concurrency} -t {max_threads} "http://{server_host}:{port}{url}"
  36. sleep 5
  37. for c in {interval}
  38. do
  39. echo ""
  40. echo "---------------------------------------------------------"
  41. echo " Concurrency: $c for {name}"
  42. echo " {wrk} {headers} {pipeline} -d {duration} -c $c -t $(($c>{max_threads}?{max_threads}:$c)) \"http://{server_host}:{port}{url}\""
  43. echo "---------------------------------------------------------"
  44. echo ""
  45. {wrk} {headers} {pipeline} -d {duration} -c "$c" -t "$(($c>{max_threads}?{max_threads}:$c))" http://{server_host}:{port}{url}
  46. sleep 2
  47. done
  48. """
  49. query_template = """
  50. echo ""
  51. echo "---------------------------------------------------------"
  52. echo " Running Primer {name}"
  53. echo " wrk {headers} -d 5 -c 8 -t 8 \"http://{server_host}:{port}{url}2\""
  54. echo "---------------------------------------------------------"
  55. echo ""
  56. wrk {headers} -d 5 -c 8 -t 8 "http://{server_host}:{port}{url}2"
  57. sleep 5
  58. echo ""
  59. echo "---------------------------------------------------------"
  60. echo " Running Warmup {name}"
  61. echo " wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} \"http://{server_host}:{port}{url}2\""
  62. echo "---------------------------------------------------------"
  63. echo ""
  64. wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} "http://{server_host}:{port}{url}2"
  65. sleep 5
  66. for c in {interval}
  67. do
  68. echo ""
  69. echo "---------------------------------------------------------"
  70. echo " Queries: $c for {name}"
  71. echo " wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} \"http://{server_host}:{port}{url}$c\""
  72. echo "---------------------------------------------------------"
  73. echo ""
  74. wrk {headers} -d {duration} -c {max_concurrency} -t {max_threads} "http://{server_host}:{port}{url}$c"
  75. sleep 2
  76. done
  77. """
  78. language = None
  79. platform = None
  80. webserver = None
  81. classification = None
  82. database = None
  83. approach = None
  84. orm = None
  85. framework = None
  86. os = None
  87. database_os = None
  88. display_name = None
  89. notes = None
  90. versus = None
  91. ############################################################
  92. # Test Variables
  93. ############################################################
  94. JSON = "json"
  95. DB = "db"
  96. QUERY = "query"
  97. FORTUNE = "fortune"
  98. UPDATE = "update"
  99. PLAINTEXT = "plaintext"
  100. ##########################################################################################
  101. # Public Methods
  102. ##########################################################################################
  103. ############################################################
  104. # Validates the jsonString is a JSON object with a 'message'
  105. # key with the value "hello, world!" (case-insensitive).
  106. ############################################################
  107. def validateJson(self, jsonString, out, err):
  108. try:
  109. obj = json.loads(jsonString)
  110. if not obj:
  111. return False
  112. if not obj["message"]:
  113. return False
  114. if not obj["message"].lower() == "hello, world!":
  115. return False
  116. return True
  117. except:
  118. err.write(textwrap.dedent("""
  119. -----------------------------------------------------
  120. Error: validateJson raised exception
  121. -----------------------------------------------------
  122. {trace}
  123. """.format( trace=sys.exc_info()[:2])))
  124. return False
  125. ############################################################
  126. # Validates the jsonString is a JSON object that has an "id"
  127. # and a "randomNumber" key, and that both keys map to
  128. # integers.
  129. ############################################################
  130. def validateDb(self, jsonString, out, err):
  131. try:
  132. obj = json.loads(jsonString)
  133. if not obj:
  134. return False
  135. if type(obj) != dict:
  136. return False
  137. if not obj["id"] or type(obj["id"]) != int:
  138. return False
  139. if not obj["randomNumber"] or type(obj["randomNumber"]) != int:
  140. return False
  141. return True
  142. except:
  143. err.write(textwrap.dedent("""
  144. -----------------------------------------------------
  145. Error: validateDb raised exception
  146. -----------------------------------------------------
  147. {trace}
  148. """.format( trace=sys.exc_info()[:2])))
  149. return False
  150. ############################################################
  151. # Validates the jsonString is an array with a length of
  152. # 2, that each entry in the array is a JSON object, that
  153. # each object has an "id" and a "randomNumber" key, and that
  154. # both keys map to integers.
  155. ############################################################
  156. def validateQuery(self, jsonString, out, err):
  157. try:
  158. arr = json.loads(jsonString)
  159. if not arr or len(arr) != 2 or type(arr[0]) != dict or type(arr[1]) != dict:
  160. return False
  161. if not arr[0]["id"] or type(arr[0]["id"]) != int:
  162. return False
  163. if not arr[0]["randomNumber"] or type(arr[0]["randomNumber"]) != int:
  164. return False
  165. if not arr[1]["id"] or type(arr[1]["id"]) != int:
  166. return False
  167. if not arr[1]["randomNumber"] or type(arr[1]["randomNumber"]) != int:
  168. return False
  169. return True
  170. except:
  171. err.write(textwrap.dedent("""
  172. -----------------------------------------------------
  173. Error: validateQuery raised exception
  174. -----------------------------------------------------
  175. {trace}
  176. """.format( trace=sys.exc_info()[:2])))
  177. return False
  178. ############################################################
  179. # Parses the given HTML string and asks a FortuneHTMLParser
  180. # whether the parsed string is a valid fortune return.
  181. ############################################################
  182. def validateFortune(self, htmlString, out, err):
  183. try:
  184. parser = FortuneHTMLParser()
  185. parser.feed(htmlString)
  186. return parser.isValidFortune()
  187. except:
  188. err.write(textwrap.dedent("""
  189. -----------------------------------------------------
  190. Error: validateFortune raised exception
  191. -----------------------------------------------------
  192. {trace}
  193. """.format( trace=sys.exc_info()[:2])))
  194. return False
  195. ############################################################
  196. # Validates the jsonString is an array with a length of
  197. # 2, that each entry in the array is a JSON object, that
  198. # each object has an "id" and a "randomNumber" key, and that
  199. # both keys map to integers.
  200. ############################################################
  201. def validateUpdate(self, jsonString, out, err):
  202. try:
  203. arr = json.loads(jsonString)
  204. if not arr or len(arr) != 2 or type(arr[0]) != dict or type(arr[1]) != dict:
  205. return False
  206. if not arr[0]["id"] or type(arr[0]["id"]) != int:
  207. return False
  208. if not arr[0]["randomNumber"] or type(arr[0]["randomNumber"]) != int:
  209. return False
  210. if not arr[1]["id"] or type(arr[1]["id"]) != int:
  211. return False
  212. if not arr[1]["randomNumber"] or type(arr[1]["randomNumber"]) != int:
  213. return False
  214. return True
  215. except:
  216. err.write(textwrap.dedent("""
  217. -----------------------------------------------------
  218. Error: validateUpdate raised exception
  219. -----------------------------------------------------
  220. {trace}
  221. """.format( trace=sys.exc_info()[:2])))
  222. return False
  223. ############################################################
  224. #
  225. ############################################################
  226. def validatePlaintext(self, jsonString, out, err):
  227. try:
  228. return jsonString.lower().strip() == "hello, world!"
  229. except:
  230. err.write(textwrap.dedent("""
  231. -----------------------------------------------------
  232. Error: validatePlaintext raised exception
  233. -----------------------------------------------------
  234. {trace}
  235. """.format( trace=sys.exc_info()[:2])))
  236. return False
  237. ############################################################
  238. # start(benchmarker)
  239. # Start the test using it's setup file
  240. ############################################################
  241. def start(self, out, err):
  242. return self.setup_module.start(self.benchmarker, out, err)
  243. ############################################################
  244. # End start
  245. ############################################################
  246. ############################################################
  247. # stop(benchmarker)
  248. # Stops the test using it's setup file
  249. ############################################################
  250. def stop(self, out, err):
  251. return self.setup_module.stop(out, err)
  252. ############################################################
  253. # End stop
  254. ############################################################
  255. ############################################################
  256. # verify_urls
  257. # Verifys each of the URLs for this test. THis will sinply
  258. # curl the URL and check for it's return status.
  259. # For each url, a flag will be set on this object for whether
  260. # or not it passed
  261. ############################################################
  262. def verify_urls(self, out, err):
  263. # JSON
  264. if self.runTests[self.JSON]:
  265. out.write( "VERIFYING JSON (" + self.json_url + ") ... " )
  266. out.flush()
  267. try:
  268. url = self.benchmarker.generate_url(self.json_url, self.port)
  269. output = self.__curl_url(url, self.JSON, out, err)
  270. if self.validateJson(output, out, err):
  271. self.json_url_passed = True
  272. else:
  273. self.json_url_passed = False
  274. except (AttributeError, subprocess.CalledProcessError) as e:
  275. self.json_url_passed = False
  276. if self.json_url_passed
  277. out.write("PASS\n")
  278. else:
  279. out.write("FAIL\n")
  280. out.flush
  281. # DB
  282. if self.runTests[self.DB]:
  283. out.write( "VERIFYING DB (" + self.db_url + ") ... " )
  284. out.flush()
  285. try:
  286. url = self.benchmarker.generate_url(self.db_url, self.port)
  287. output = self.__curl_url(url, self.DB, out, err)
  288. if self.validateDb(output, out, err):
  289. self.db_url_passed = True
  290. else:
  291. self.db_url_passed = False
  292. except (AttributeError, subprocess.CalledProcessError) as e:
  293. self.db_url_passed = False
  294. if self.db_url_passed
  295. out.write("PASS\n")
  296. else:
  297. out.write("FAIL\n")
  298. out.flush
  299. # Query
  300. if self.runTests[self.QUERY]:
  301. out.write( "VERIFYING QUERY (" + self.query_url + "2) ... " )
  302. out.flush()
  303. try:
  304. url = self.benchmarker.generate_url(self.query_url + "2", self.port)
  305. output = self.__curl_url(url, self.QUERY, out, err)
  306. if self.validateQuery(output, out, err):
  307. self.query_url_passed = True
  308. else:
  309. self.query_url_passed = False
  310. except (AttributeError, subprocess.CalledProcessError) as e:
  311. self.query_url_passed = False
  312. if self.query_url_passed
  313. out.write("PASS\n")
  314. else:
  315. out.write("FAIL\n")
  316. out.flush
  317. # Fortune
  318. if self.runTests[self.FORTUNE]:
  319. out.write( "VERIFYING FORTUNE (" + self.fortune_url + ") ... " )
  320. out.flush()
  321. try:
  322. url = self.benchmarker.generate_url(self.fortune_url, self.port)
  323. output = self.__curl_url(url, self.FORTUNE, out, err)
  324. if self.validateFortune(output, out, err):
  325. self.fortune_url_passed = True
  326. else:
  327. self.fortune_url_passed = False
  328. except (AttributeError, subprocess.CalledProcessError) as e:
  329. self.fortune_url_passed = False
  330. if self.fortune_url_passed
  331. out.write("PASS\n")
  332. else:
  333. out.write("FAIL\n")
  334. out.flush
  335. # Update
  336. if self.runTests[self.UPDATE]:
  337. out.write( "VERIFYING UPDATE (" + self.update_url + "2) ... " )
  338. out.flush()
  339. try:
  340. url = self.benchmarker.generate_url(self.update_url + "2", self.port)
  341. output = self.__curl_url(url, self.UPDATE, out, err)
  342. if self.validateUpdate(output, out, err):
  343. self.update_url_passed = True
  344. else:
  345. self.update_url_passed = False
  346. except (AttributeError, subprocess.CalledProcessError) as e:
  347. self.update_url_passed = False
  348. if self.update_url_passed
  349. out.write("PASS\n")
  350. else:
  351. out.write("FAIL\n")
  352. out.flush
  353. # plaintext
  354. if self.runTests[self.PLAINTEXT]:
  355. out.write( "VERIFYING PLAINTEXT (" + self.plaintext_url + ") ... " )
  356. out.flush()
  357. try:
  358. url = self.benchmarker.generate_url(self.plaintext_url, self.port)
  359. output = self.__curl_url(url, self.PLAINTEXT, out, err)
  360. if self.validatePlaintext(output, out, err):
  361. self.plaintext_url_passed = True
  362. else:
  363. self.plaintext_url_passed = False
  364. except (AttributeError, subprocess.CalledProcessError) as e:
  365. self.plaintext_url_passed = False
  366. if self.plaintext_url_passed
  367. out.write("PASS\n")
  368. else:
  369. out.write("FAIL\n")
  370. out.flush
  371. ############################################################
  372. # End verify_urls
  373. ############################################################
  374. ############################################################
  375. # contains_type(type)
  376. # true if this test contains an implementation of the given
  377. # test type (json, db, etc.)
  378. ############################################################
  379. def contains_type(self, type):
  380. try:
  381. if type == self.JSON and self.json_url != None:
  382. return True
  383. if type == self.DB and self.db_url != None:
  384. return True
  385. if type == self.QUERY and self.query_url != None:
  386. return True
  387. if type == self.FORTUNE and self.fortune_url != None:
  388. return True
  389. if type == self.UPDATE and self.update_url != None:
  390. return True
  391. if type == self.PLAINTEXT and self.plaintext_url != None:
  392. return True
  393. except AttributeError:
  394. pass
  395. return False
  396. ############################################################
  397. # End stop
  398. ############################################################
  399. ############################################################
  400. # benchmark
  401. # Runs the benchmark for each type of test that it implements
  402. # JSON/DB/Query.
  403. ############################################################
  404. def benchmark(self, out, err):
  405. # JSON
  406. if self.runTests[self.JSON]:
  407. try:
  408. if self.benchmarker.type == "all" or self.benchmarker.type == self.JSON:
  409. out.write("BENCHMARKING JSON ... ")
  410. out.flush()
  411. results = None
  412. if self.json_url_passed:
  413. remote_script = self.__generate_concurrency_script(self.json_url, self.port, self.accept_json)
  414. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.JSON), err)
  415. results = self.__parse_test(self.JSON)
  416. else:
  417. results = dict()
  418. results['results'] = []
  419. self.benchmarker.report_results(framework=self, test=self.JSON, results=results['results'], passed=self.json_url_passed)
  420. out.write( "Complete\n" )
  421. out.flush()
  422. except AttributeError:
  423. pass
  424. # DB
  425. if self.runTests[self.DB]:
  426. try:
  427. if self.benchmarker.type == "all" or self.benchmarker.type == self.DB:
  428. out.write("BENCHMARKING DB ... ")
  429. out.flush()
  430. results = None
  431. if self.db_url_passed:
  432. remote_script = self.__generate_concurrency_script(self.db_url, self.port, self.accept_json)
  433. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.DB), err)
  434. results = self.__parse_test(self.DB)
  435. else:
  436. results = dict()
  437. results['results'] = []
  438. self.benchmarker.report_results(framework=self, test=self.DB, results=results['results'], passed=self.db_url_passed)
  439. out.write( "Complete\n" )
  440. except AttributeError:
  441. pass
  442. # Query
  443. if self.runTests[self.QUERY]:
  444. try:
  445. if self.benchmarker.type == "all" or self.benchmarker.type == self.QUERY:
  446. out.write("BENCHMARKING Query ... ")
  447. out.flush()
  448. results = None
  449. if self.query_url_passed:
  450. remote_script = self.__generate_query_script(self.query_url, self.port, self.accept_json)
  451. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.QUERY), err)
  452. results = self.__parse_test(self.QUERY)
  453. else:
  454. results = dict()
  455. results['results'] = []
  456. self.benchmarker.report_results(framework=self, test=self.QUERY, results=results['results'], passed=self.query_url_passed)
  457. out.write( "Complete\n" )
  458. out.flush()
  459. except AttributeError:
  460. pass
  461. # fortune
  462. if self.runTests[self.FORTUNE]:
  463. try:
  464. if self.benchmarker.type == "all" or self.benchmarker.type == self.FORTUNE:
  465. out.write("BENCHMARKING Fortune ... ")
  466. out.flush()
  467. results = None
  468. if self.fortune_url_passed:
  469. remote_script = self.__generate_concurrency_script(self.fortune_url, self.port, self.accept_html)
  470. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.FORTUNE), err)
  471. results = self.__parse_test(self.FORTUNE)
  472. else:
  473. results = dict()
  474. results['results'] = []
  475. self.benchmarker.report_results(framework=self, test=self.FORTUNE, results=results['results'], passed=self.fortune_url_passed)
  476. out.write( "Complete\n" )
  477. out.flush()
  478. except AttributeError:
  479. pass
  480. # update
  481. if self.runTests[self.UPDATE]:
  482. try:
  483. if self.benchmarker.type == "all" or self.benchmarker.type == self.UPDATE:
  484. out.write("BENCHMARKING Update ... ")
  485. out.flush()
  486. results = None
  487. if self.update_url_passed:
  488. remote_script = self.__generate_query_script(self.update_url, self.port, self.accept_json)
  489. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.UPDATE), err)
  490. results = self.__parse_test(self.UPDATE)
  491. else:
  492. results = dict()
  493. results['results'] = []
  494. self.benchmarker.report_results(framework=self, test=self.UPDATE, results=results['results'], passed=self.update_url_passed)
  495. out.write( "Complete\n" )
  496. out.flush()
  497. except AttributeError:
  498. pass
  499. # plaintext
  500. if self.runTests[self.PLAINTEXT]:
  501. try:
  502. if self.benchmarker.type == "all" or self.benchmarker.type == self.PLAINTEXT:
  503. out.write("BENCHMARKING Plaintext ... ")
  504. out.flush()
  505. results = None
  506. if self.plaintext_url_passed:
  507. 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")
  508. self.__run_benchmark(remote_script, self.benchmarker.output_file(self.name, self.PLAINTEXT), err)
  509. results = self.__parse_test(self.PLAINTEXT)
  510. else:
  511. results = dict()
  512. results['results'] = []
  513. self.benchmarker.report_results(framework=self, test=self.PLAINTEXT, results=results['results'], passed=self.plaintext_url_passed)
  514. out.write( "Complete\n" )
  515. out.flush()
  516. except AttributeError:
  517. traceback.print_exc()
  518. pass
  519. ############################################################
  520. # End benchmark
  521. ############################################################
  522. ############################################################
  523. # parse_all
  524. # Method meant to be run for a given timestamp
  525. ############################################################
  526. def parse_all(self):
  527. # JSON
  528. if os.path.exists(self.benchmarker.output_file(self.name, self.JSON)):
  529. results = self.__parse_test(self.JSON)
  530. self.benchmarker.report_results(framework=self, test=self.JSON, results=results['results'])
  531. # DB
  532. if os.path.exists(self.benchmarker.output_file(self.name, self.DB)):
  533. results = self.__parse_test(self.DB)
  534. self.benchmarker.report_results(framework=self, test=self.DB, results=results['results'])
  535. # Query
  536. if os.path.exists(self.benchmarker.output_file(self.name, self.QUERY)):
  537. results = self.__parse_test(self.QUERY)
  538. self.benchmarker.report_results(framework=self, test=self.QUERY, results=results['results'])
  539. # Fortune
  540. if os.path.exists(self.benchmarker.output_file(self.name, self.FORTUNE)):
  541. results = self.__parse_test(self.FORTUNE)
  542. self.benchmarker.report_results(framework=self, test=self.FORTUNE, results=results['results'])
  543. # Update
  544. if os.path.exists(self.benchmarker.output_file(self.name, self.UPDATE)):
  545. results = self.__parse_test(self.UPDATE)
  546. self.benchmarker.report_results(framework=self, test=self.UPDATE, results=results['results'])
  547. # Plaintext
  548. if os.path.exists(self.benchmarker.output_file(self.name, self.PLAINTEXT)):
  549. results = self.__parse_test(self.PLAINTEXT)
  550. self.benchmarker.report_results(framework=self, test=self.PLAINTEXT, results=results['results'])
  551. ############################################################
  552. # End parse_all
  553. ############################################################
  554. ############################################################
  555. # __parse_test(test_type)
  556. ############################################################
  557. def __parse_test(self, test_type):
  558. try:
  559. results = dict()
  560. results['results'] = []
  561. with open(self.benchmarker.output_file(self.name, test_type)) as raw_data:
  562. is_warmup = True
  563. rawData = None
  564. for line in raw_data:
  565. if "Queries:" in line or "Concurrency:" in line:
  566. is_warmup = False
  567. rawData = None
  568. continue
  569. if "Warmup" in line or "Primer" in line:
  570. is_warmup = True
  571. continue
  572. if not is_warmup:
  573. if rawData == None:
  574. rawData = dict()
  575. results['results'].append(rawData)
  576. #if "Requests/sec:" in line:
  577. # m = re.search("Requests/sec:\s+([0-9]+)", line)
  578. # rawData['reportedResults'] = m.group(1)
  579. # search for weighttp data such as succeeded and failed.
  580. if "Latency" in line:
  581. m = re.findall("([0-9]+\.*[0-9]*[us|ms|s|m|%]+)", line)
  582. if len(m) == 4:
  583. rawData['latencyAvg'] = m[0]
  584. rawData['latencyStdev'] = m[1]
  585. rawData['latencyMax'] = m[2]
  586. # rawData['latencyStdevPercent'] = m[3]
  587. #if "Req/Sec" in line:
  588. # m = re.findall("([0-9]+\.*[0-9]*[k|%]*)", line)
  589. # if len(m) == 4:
  590. # rawData['requestsAvg'] = m[0]
  591. # rawData['requestsStdev'] = m[1]
  592. # rawData['requestsMax'] = m[2]
  593. # rawData['requestsStdevPercent'] = m[3]
  594. #if "requests in" in line:
  595. # m = re.search("requests in ([0-9]+\.*[0-9]*[ms|s|m|h]+)", line)
  596. # if m != None:
  597. # # parse out the raw time, which may be in minutes or seconds
  598. # raw_time = m.group(1)
  599. # if "ms" in raw_time:
  600. # rawData['total_time'] = float(raw_time[:len(raw_time)-2]) / 1000.0
  601. # elif "s" in raw_time:
  602. # rawData['total_time'] = float(raw_time[:len(raw_time)-1])
  603. # elif "m" in raw_time:
  604. # rawData['total_time'] = float(raw_time[:len(raw_time)-1]) * 60.0
  605. # elif "h" in raw_time:
  606. # rawData['total_time'] = float(raw_time[:len(raw_time)-1]) * 3600.0
  607. if "requests in" in line:
  608. m = re.search("([0-9]+) requests in", line)
  609. if m != None:
  610. rawData['totalRequests'] = int(m.group(1))
  611. if "Socket errors" in line:
  612. if "connect" in line:
  613. m = re.search("connect ([0-9]+)", line)
  614. rawData['connect'] = int(m.group(1))
  615. if "read" in line:
  616. m = re.search("read ([0-9]+)", line)
  617. rawData['read'] = int(m.group(1))
  618. if "write" in line:
  619. m = re.search("write ([0-9]+)", line)
  620. rawData['write'] = int(m.group(1))
  621. if "timeout" in line:
  622. m = re.search("timeout ([0-9]+)", line)
  623. rawData['timeout'] = int(m.group(1))
  624. if "Non-2xx" in line:
  625. m = re.search("Non-2xx or 3xx responses: ([0-9]+)", line)
  626. if m != None:
  627. rawData['5xx'] = int(m.group(1))
  628. return results
  629. except IOError:
  630. return None
  631. ############################################################
  632. # End benchmark
  633. ############################################################
  634. ##########################################################################################
  635. # Private Methods
  636. ##########################################################################################
  637. ############################################################
  638. # __run_benchmark(script, output_file)
  639. # Runs a single benchmark using the script which is a bash
  640. # template that uses weighttp to run the test. All the results
  641. # outputed to the output_file.
  642. ############################################################
  643. def __run_benchmark(self, script, output_file, err):
  644. with open(output_file, 'w') as raw_file:
  645. p = subprocess.Popen(self.benchmarker.client_ssh_string.split(" "), stdin=subprocess.PIPE, stdout=raw_file, stderr=err)
  646. p.communicate(script)
  647. err.flush()
  648. ############################################################
  649. # End __run_benchmark
  650. ############################################################
  651. ############################################################
  652. # __generate_concurrency_script(url, port)
  653. # Generates the string containing the bash script that will
  654. # be run on the client to benchmark a single test. This
  655. # specifically works for the variable concurrency tests (JSON
  656. # and DB)
  657. ############################################################
  658. def __generate_concurrency_script(self, url, port, accept_header, wrk_command="wrk", intervals=[], pipeline=""):
  659. if len(intervals) == 0:
  660. intervals = self.benchmarker.concurrency_levels
  661. headers = self.__get_request_headers(accept_header)
  662. return self.concurrency_template.format(max_concurrency=self.benchmarker.max_concurrency,
  663. max_threads=self.benchmarker.max_threads, name=self.name, duration=self.benchmarker.duration,
  664. interval=" ".join("{}".format(item) for item in intervals),
  665. server_host=self.benchmarker.server_host, port=port, url=url, headers=headers, wrk=wrk_command,
  666. pipeline=pipeline)
  667. ############################################################
  668. # End __generate_concurrency_script
  669. ############################################################
  670. ############################################################
  671. # __generate_query_script(url, port)
  672. # Generates the string containing the bash script that will
  673. # be run on the client to benchmark a single test. This
  674. # specifically works for the variable query tests (Query)
  675. ############################################################
  676. def __generate_query_script(self, url, port, accept_header):
  677. headers = self.__get_request_headers(accept_header)
  678. return self.query_template.format(max_concurrency=self.benchmarker.max_concurrency,
  679. max_threads=self.benchmarker.max_threads, name=self.name, duration=self.benchmarker.duration,
  680. interval=" ".join("{}".format(item) for item in self.benchmarker.query_intervals),
  681. server_host=self.benchmarker.server_host, port=port, url=url, headers=headers)
  682. ############################################################
  683. # End __generate_query_script
  684. ############################################################
  685. ############################################################
  686. # __get_request_headers(accept_header)
  687. # Generates the complete HTTP header string
  688. ############################################################
  689. def __get_request_headers(self, accept_header):
  690. return self.headers_template.format(accept=accept_header)
  691. ############################################################
  692. # End __format_request_headers
  693. ############################################################
  694. ############################################################
  695. # __curl_url
  696. # Dump HTTP response and headers. Throw exception if there
  697. # is an HTTP error.
  698. ############################################################
  699. def __curl_url(self, url, testType, out, err):
  700. # Use -i to output response with headers.
  701. # Don't use -f so that the HTTP response code is ignored.
  702. # Use --stderr - to redirect stderr to stdout so we get
  703. # error output for sure in stdout.
  704. # Use -sS to hide progress bar, but show errors.
  705. subprocess.check_call(["curl", "-i", "-sS", url], stderr=err, stdout=out)
  706. out.flush()
  707. err.flush()
  708. # HTTP output may not end in a newline, so add that here.
  709. out.write( "\n" )
  710. out.flush()
  711. # We need to get the respond body from the curl and return it.
  712. p = subprocess.Popen(["curl", "-s", url], stdout=subprocess.PIPE)
  713. output = p.communicate()
  714. # In the curl invocation above we could not use -f because
  715. # then the HTTP response would not be output, so use -f in
  716. # an additional invocation so that if there is an HTTP error,
  717. # subprocess.CalledProcessError will be thrown. Note that this
  718. # uses check_output() instead of check_call() so that we can
  719. # ignore the HTTP response because we already output that in
  720. # the first curl invocation.
  721. subprocess.check_output(["curl", "-fsS", url], stderr=err)
  722. out.flush()
  723. err.flush()
  724. # HTTP output may not end in a newline, so add that here.
  725. out.write( "\n" )
  726. out.flush()
  727. if output:
  728. # We have the response body - return it
  729. return output[0]
  730. ##############################################################
  731. # End __curl_url
  732. ##############################################################
  733. ##########################################################################################
  734. # Constructor
  735. ##########################################################################################
  736. def __init__(self, name, directory, benchmarker, runTests, args):
  737. self.name = name
  738. self.directory = directory
  739. self.benchmarker = benchmarker
  740. self.runTests = runTests
  741. self.__dict__.update(args)
  742. # ensure directory has __init__.py file so that we can use it as a Python package
  743. if not os.path.exists(os.path.join(directory, "__init__.py")):
  744. open(os.path.join(directory, "__init__.py"), 'w').close()
  745. self.setup_module = setup_module = importlib.import_module(directory + '.' + self.setup_file)
  746. ############################################################
  747. # End __init__
  748. ############################################################
  749. ############################################################
  750. # End FrameworkTest
  751. ############################################################
  752. ##########################################################################################
  753. # Static methods
  754. ##########################################################################################
  755. ##############################################################
  756. # parse_config(config, directory, benchmarker)
  757. # parses a config file and returns a list of FrameworkTest
  758. # objects based on that config file.
  759. ##############################################################
  760. def parse_config(config, directory, benchmarker):
  761. tests = []
  762. # The config object can specify multiple tests, we neep to loop
  763. # over them and parse them out
  764. for test in config['tests']:
  765. for key, value in test.iteritems():
  766. test_name = config['framework']
  767. runTests = dict()
  768. runTests["json"] = True if value.get("json_url", False) else False
  769. runTests["db"] = True if value.get("db_url", False) else False
  770. runTests["query"] = True if value.get("query_url", False) else False
  771. runTests["fortune"] = True if value.get("fortune_url", False) else False
  772. runTests["update"] = True if value.get("update_url", False) else False
  773. runTests["plaintext"] = True if value.get("plaintext_url", False) else False
  774. # if the test uses the 'defualt' keywork, then we don't
  775. # append anything to it's name. All configs should only have 1 default
  776. if key != 'default':
  777. # we need to use the key in the test_name
  778. test_name = test_name + "-" + key
  779. tests.append(FrameworkTest(test_name, directory, benchmarker, runTests, value))
  780. return tests
  781. ##############################################################
  782. # End parse_config
  783. ##############################################################