framework_test.py 35 KB

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