framework_test.py 32 KB

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