framework_test.py 39 KB

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