framework_test.py 38 KB

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