Advertisement
Guest User

load.py

a guest
Mar 17th, 2017
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 28.70 KB | None | 0 0
  1. #!/usr/bin/python2.6
  2. #
  3. # Copyright (C) 2007 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. # Name
  18. # load.py
  19. #
  20. # Description:
  21. # Simple script for load testing the appliance.
  22. #
  23. # Usage:
  24. # There are two ways to specify the queries to use:
  25. # 1. Extract some sample queries from the search logs on your appliance
  26. # cat <logs> | awk '{ print $7 }' | sed -e 's/^\(.*\)&ip=.*$/\1/' > q.txt
  27. # You can specify queries as a full URI (e.g. /search?q=foo&...) or
  28. # as a URL-encoded query term (script will use default_collection
  29. # and default_frontend).
  30. # Then run the load testing script with the following parameters:
  31. # load.py --host <appliance-hostname> --queries <queries-file>
  32. # 2. Specify the name of a GSA search log, as well as the username and
  33. # password for the admin interface. Then, run the load testing script
  34. # as follows:
  35. # load.py --host <appliance-hostname> --query_log=<query-log-name> \
  36. # --admin_username=<username> --admin_password=<password>
  37. # Additional options are:
  38. # --port Port on the webserver (default is port 80)
  39. # --threads Number of concurrent queries (default is 3)
  40. # --suggest Enable query suggestions
  41. # --rand_suggest If --suggest is given, this flag will make a random
  42. # number of suggestion queries between 1 and the length
  43. # of the query string. If this flag is not given, then
  44. # only one suggestion query will be made.
  45. # --cluster Enable result clustering
  46. # --format Determines the report format. Can be 'text' or 'html'.
  47. # --output The report output location. If unspecified, the report
  48. # will be printed to stdout.
  49. # --charts Generate Google Charts URLs in reports for visualizing
  50. # reponse times
  51. # --auth_cfg If any of the queries are secure, then this is required.
  52. # This specifies the authentication configuration file to
  53. # read, in YAML format.
  54. # (A query is assumed to be secure if the script is
  55. # redirected to a page starting with something other than
  56. # /search, /cluster, or /suggest.
  57. # --mode Can be "once" or "benchmark".
  58. # once: The default (assumed if --mode is unspecified).
  59. # Runs the load testing once with the specified thread
  60. # count.
  61. # benchmark: Loops the test, starting with 10 threads and
  62. # increasing by 10 every iteration (this number can be
  63. # customized with --thread_step). Exits when the
  64. # failure rate exceeds the value specified by
  65. # --max_err_rate, or when the number of iterations
  66. # hits the maximum specified by --max_trials.
  67. # --thread_step For --mode=benchmark. Specifies the number of threads
  68. # to increase by for each iteration. Default is 10.
  69. # --max_err_rate Only for --mode=benchmark. This is the error
  70. # rate at which the testing stops. Should be specified
  71. # as a floating point value between 0.0 and 1.0. The default
  72. # value is 0.5.
  73. # --max_trials Also for the benchmark mode. Specifies the maximum
  74. # number of test iterations to run. The default value
  75. # is 15 trials.
  76. # --raw Treat the query file as containing raw URL (minus the
  77. # host and port)
  78. #
  79. #
  80. # Sample output from a successful query:
  81. # Fri Aug 15 13:19:43 2008: Thread-3: success: 0.3 secs query: foo
  82. #
  83. # Example auth_cfg file:
  84. # universal-login:
  85. # Default:
  86. # username: user1
  87. # password: password1
  88. # secondary:
  89. # username: user2
  90. # password: password2
  91. # forms:
  92. # http://sso.mydomain.com/login.jsp:
  93. # post: http://sso.mydomain.com/verifylogin.jsp
  94. # fields:
  95. # username: user3
  96. # password: password3
  97. # In this example, whenever the load script encounters a universal login
  98. # form, it will fill in and post the form with user1/password1 for the
  99. # Default credential group and user2/password2 for the secondary
  100. # credential group. If the script is ever redirected to an external form
  101. # for authentication called 'http://sso.mydomain.com/login.jsp', the script
  102. # will fill in the fields username/password with the values user3/password3
  103. # and will post to http://sso.mydomain.com/verifylogin.jsp.
  104. # For forms authentication, each request thread will keep its authentication
  105. # cookies (as long as they don't expire) throughout its lifetime.
  106. #
  107. #
  108. # This code is not supported by Google
  109. #
  110.  
  111.  
  112. # TODO(jlowry):
  113. # * Would be good to send a fixed qps to a GSA using Python's
  114. # built-in scheduler module.
  115. # TODO(lchabardes):
  116. # * Simplify query input: generate query set from clustering
  117. # * Make the repartition of suggest/clustering configurable
  118. # TODO(jonathanho):
  119. # * Separate secure searches from regular ones in the report output
  120.  
  121. import cookielib
  122. import getopt
  123. import logging
  124. import math
  125. import Queue
  126. import random
  127. import re
  128. import sys
  129. import threading
  130. import time
  131. import urllib
  132. import urllib2
  133. import urlparse
  134. import xml.dom.minidom
  135. import yaml
  136. from lxml import etree
  137.  
  138.  
  139. class TimeSet(object):
  140. """Holds a set of times for requests, and generates pretty visualizations."""
  141.  
  142. def __init__(self, name):
  143. self.good_times = []
  144. self.error_count = 0
  145. self.name = name
  146.  
  147. def Extend(self, ts):
  148. self.good_times.extend(ts.good_times)
  149. self.error_count += ts.error_count
  150.  
  151. def AddGood(self, t):
  152. self.good_times.append(t)
  153.  
  154. def AddError(self):
  155. self.error_count += 1
  156.  
  157. def NumGood(self):
  158. return len(self.good_times)
  159.  
  160. def NumError(self):
  161. return self.error_count
  162.  
  163. def NumTotal(self):
  164. return len(self.good_times) + self.error_count
  165.  
  166. def ErrorRate(self):
  167. return float(self.NumError()) / float(self.NumTotal())
  168.  
  169. def MinGood(self):
  170. return min(self.good_times)
  171.  
  172. def MeanGood(self):
  173. return sum(self.good_times) / float(self.NumGood())
  174.  
  175. def MedianGood(self):
  176. self.good_times.sort()
  177. l = self.NumGood()
  178. if l % 2 == 1:
  179. return self.good_times[(l - 1)/2]
  180. return (self.good_times[l/2 - 1] + self.good_times[l/2]) / 2.0
  181.  
  182. def MaxGood(self):
  183. return max(self.good_times)
  184.  
  185. def StdDevGood(self):
  186. mean = self.MeanGood()
  187. s = 0.0
  188. for t in self.good_times:
  189. s += (t - mean)**2
  190. return math.sqrt(s / float(self.NumGood()))
  191.  
  192. def ChartURLGood(self):
  193. """Generates a histogram with Google Charts for the good response data.
  194.  
  195. Returns:
  196. The URL (a string).
  197. """
  198. self.good_times.sort()
  199. total_min, total_max = self.good_times[0], self.good_times[-1]
  200. total_range = total_max - total_min
  201. # calculate the height of each bin
  202. nbins = int(math.ceil(math.sqrt(len(self.good_times))))
  203. nbins = min(nbins, 13) # the Charts API seems to have a bar cap
  204. width = total_range / float(nbins)
  205. # fill in the bins
  206. bins = [0] * nbins
  207. curr_bin = 0
  208. bin_min, bin_max = total_min, total_min + width
  209. for t in self.good_times:
  210. # move along till we get to the correct bin
  211. while (t < bin_min or t >= bin_max) and curr_bin < nbins:
  212. curr_bin += 1
  213. bin_min, bin_max = bin_max, bin_max + width
  214. # if we get the last item by itself at the end, put it in the last bucket
  215. if curr_bin >= nbins:
  216. bins[-1] += 1
  217. break
  218. bins[curr_bin] += 1
  219. # generate the google charts url and return it
  220. chart_data = ",".join(str(b) for b in bins)
  221. return ("http://chart.apis.google.com/chart?"
  222. "cht=bvs&"
  223. "chs=450x400&"
  224. "chxt=x,y,x,y&"
  225. "chxr=0,%.2f,%.2f,%.2f|1,%d,%d&"
  226. "chxl=2:|Time+(s)|3:|Queries&"
  227. "chxp=2,50|3,50&"
  228. "chtt=Distribution+of+Query+Times+for+%s&"
  229. "chd=t:%s&"
  230. "chds=0,%d") % (
  231. total_min, total_max, width,
  232. min(bins), max(bins),
  233. self.name,
  234. chart_data,
  235. max(bins))
  236.  
  237. def Report(self, chart, format):
  238. """Generates a report of the data.
  239.  
  240. Args:
  241. chart: A boolean that determines if this will output a Google Charts URL.
  242. format; A string, 'text' or 'html'.
  243.  
  244. Returns:
  245. The report string.
  246. """
  247. chartstr = ""
  248. if chart:
  249. url = self.ChartURLGood()
  250. if format == 'html':
  251. url = '<img src="%s" />' % url
  252. chartstr = " Histogram of 200s:\n %s\n" % url
  253. rep = "Stats for %s:\n" % self.name
  254. if format == 'html':
  255. rep = '<h2>%s</h2>' % rep
  256. rep += (" Number of responses:\n"
  257. " 200: %s\n"
  258. " errors: %s\n"
  259. " Latency:\n"
  260. " median: %.2f secs\n"
  261. " maximum: %.2f secs\n"
  262. " std dev: %.2f secs\n"
  263. "%s") % (
  264. self.NumGood(), self.NumError(), self.MedianGood(),
  265. self.MaxGood(), self.StdDevGood(), chartstr)
  266. if format == 'html':
  267. rep = rep.replace('\n', '<br/>')
  268. return rep
  269.  
  270.  
  271. class Results(object):
  272. """Class for holding results of load tests."""
  273.  
  274. def __init__(self, gen_charts, format):
  275. self.search_times = TimeSet("Search")
  276. self.cluster_times = TimeSet("Cluster")
  277. self.suggest_times = TimeSet("Suggest")
  278. self.start = time.time()
  279. self.gen_charts = gen_charts
  280. self.format = format
  281.  
  282. def Summary(self):
  283. """Summarizes the results.
  284.  
  285. Returns:
  286. A string, summarizing the results
  287. """
  288. end = time.time()
  289. total_200 = (self.search_times.NumGood() + self.cluster_times.NumGood() +
  290. self.suggest_times.NumGood())
  291. total_time = end - self.start
  292. av_qps = total_200 / total_time
  293.  
  294. summary = "Load test report:\n"
  295. if self.format == 'html':
  296. summary = '<h1>%s</h1>' % summary
  297.  
  298. if self.cluster_times.NumTotal():
  299. summary += self.cluster_times.Report(self.gen_charts, self.format)
  300. if self.suggest_times.NumTotal():
  301. summary += self.suggest_times.Report(self.gen_charts, self.format)
  302. summary += self.search_times.Report(self.gen_charts, self.format)
  303.  
  304. overall = self.OverallTimes()
  305. summary += overall.Report(self.gen_charts, self.format)
  306. summary += " Average QPS: %f\n" % av_qps
  307. return summary
  308.  
  309. def OverallTimes(self):
  310. """Generates a TimeSet containing all search types.
  311.  
  312. Returns:
  313. A TimeSet.
  314. """
  315. overall = TimeSet("Overall")
  316. overall.Extend(self.cluster_times)
  317. overall.Extend(self.suggest_times)
  318. overall.Extend(self.search_times)
  319. return overall
  320.  
  321.  
  322. class Client(threading.Thread):
  323.  
  324. def __init__(self, host, port, queries, res, enable_cluster, enable_suggest,
  325. rand_suggest, auth_cfg, raw):
  326. threading.Thread.__init__(self)
  327. self.host = host
  328. self.port = port
  329. self.res = res
  330. self.queries = queries
  331. self.enable_cluster = enable_cluster
  332. self.enable_suggest = enable_suggest
  333. self.rand_suggest = rand_suggest
  334. self.auth_cfg = auth_cfg
  335. self.cookies = cookielib.LWPCookieJar()
  336. cookie_processor = urllib2.HTTPCookieProcessor(self.cookies)
  337. self.opener = urllib2.build_opener(cookie_processor)
  338. # when scanning for the credential groups in the universal login page,
  339. # match the CSS, which will have something like
  340. # #<group name>Active
  341. self.credgrp_re = re.compile(r"#(.*)Active")
  342. self.raw = raw
  343.  
  344. def run(self):
  345. while True:
  346. try:
  347. q = self.queries.get(block=False)
  348. except Queue.Empty:
  349. break
  350. else:
  351. parameters = dict()
  352. if q.find("/search?") >= 0:
  353. query_parsed = urlparse.urlparse(q.strip())
  354. query_parsed_clean = query_parsed[4]
  355. #Cleaning up query input to prevent invalid requests.
  356. if query_parsed_clean[0] == "&":
  357. query_parsed_clean = query_parsed_clean[1:-1]
  358. query_terms = []
  359. for qterm in query_parsed_clean.split("&"):
  360. if qterm.find("=") != -1:
  361. query_terms.append(qterm)
  362. try:
  363. parameters = dict([param.split("=") for param in query_terms])
  364. except Exception, e:
  365. print e
  366. print query_parsed
  367. print parameters
  368. raise
  369. if 'q' in parameters:
  370. self.FetchContent(self.host, self.port, q.strip(), parameters)
  371. else:
  372. self.FetchContent(self.host, self.port, q.strip(), parameters)
  373.  
  374. def Request(self, host, port, method, req):
  375. def TimedRequest(url, data):
  376. start = time.time()
  377. resp = self.opener.open(url, data)
  378. end = time.time()
  379. return (resp, end - start)
  380.  
  381. # make urllib2 use the method we want
  382. data = None
  383. if method == "POST":
  384. data = ""
  385.  
  386. if self.raw != True:
  387. result = TimedRequest("http://%s:%s%s" % (host, port, req), data)
  388. else:
  389. result = TimedRequest(req, data)
  390.  
  391. resp = result[0]
  392.  
  393. # check if we've ended up at the actual search results page
  394. # if not, then assume we've landed at some sort of auth page
  395. respurl = urlparse.urlparse(resp.geturl())
  396. if self.raw != True and (respurl[2] != "/search" and respurl[2] != "/suggest" and
  397. respurl[2] != "/cluster"):
  398. if not self.auth_cfg:
  399. raise urllib2.URLError("No auth cfg found, needed for %s" % req)
  400.  
  401. # the URL we're now at, minus all the GET parameters
  402. path = "%s://%s%s" % (respurl[0], respurl[1], respurl[2])
  403.  
  404. # now find out what kind of auth page we're at
  405. # case 1: universal login
  406. if respurl[2] == "/security-manager/samlauthn":
  407. # scan through the form to see the credential groups wanted
  408. credgrps = []
  409. for match in self.credgrp_re.finditer(resp.read()):
  410. credgrps.append(match.group(1))
  411. # construct the POST fields
  412. formdata = {}
  413. for credgrp in credgrps:
  414. ufield = "u" + credgrp
  415. pwfield = "pw" + credgrp
  416. grpdata = self.auth_cfg["universal-login"][credgrp]
  417. formdata[ufield] = grpdata["username"]
  418. formdata[pwfield] = grpdata["password"]
  419. # post it
  420. result = TimedRequest(path, urllib.urlencode(formdata))
  421.  
  422. # case 2: forms-based login
  423. elif path in self.auth_cfg["forms"]:
  424. form = self.auth_cfg["forms"][path]
  425. result = TimedRequest(form["post"], urllib.urlencode(form["fields"]))
  426.  
  427. # unsupported/unknown auth mech
  428. else:
  429. raise urllib2.URLError("Login unrecognized: %s" % respurl.geturl())
  430.  
  431. # if we're still not at the results page, the auth probably failed
  432. if urlparse.urlparse(result[0].geturl())[2] != "/search":
  433. raise urllib2.URLError("Auth failed: %s" % req)
  434.  
  435. return result
  436.  
  437. def FetchContent(self, host, port, q, parameters):
  438. start_time = time.ctime(time.time())
  439. test_requests = []
  440. #For each queries we get from the queue, making one suggest query and one clustering req:
  441. if self.enable_cluster == True:
  442. token = parameters.get('q')
  443. frontend = parameters.get('client', "default_frontend")
  444. collection = parameters.get('site', "default_collection")
  445. test_requests.append("/cluster?coutput=json&q=%s&site=%s&client=%s" %(token, collection, frontend))
  446. if self.enable_suggest == True:
  447. token = parameters.get('q')
  448. frontend = parameters.get('client', "default_frontend")
  449. collection = parameters.get('site', "default_collection")
  450. sugtokens = []
  451. if self.rand_suggest:
  452. # generate a random number of suggestion queries
  453. # between 1 and the token length
  454. nsug = random.randint(1, len(token))
  455. sugsize = (len(token) + nsug - 1) / nsug
  456. for i in range(0, len(token), sugsize):
  457. sugtokens.append(token[i:i+sugsize])
  458. else:
  459. sugtokens.append(token[0:2])
  460. for s in sugtokens:
  461. test_requests.append("/suggest?q=%s&max_matches=10&use_similar&"
  462. "access=p&format=rich" % s)
  463. if self.raw or (q.find("/search?") == 0 and q.find("coutput=json") != 0):
  464. test_requests.append(q)
  465. else:
  466. test_requests.append("/search?q=%s&output=xml_no_dtd&client=default_frontend&roxystylesheet=default_frontend&site=default_collection" % (q))
  467. for req in test_requests:
  468. try:
  469. if req.find("/suggest?") == 0 or req.find("/cluster?") == 0:
  470. res, exec_time = self.Request(host, port, "POST", req)
  471. else:
  472. res, exec_time = self.Request(host, port, "GET", req)
  473. logging.info("%s: %s: success: %.1f secs query: %s", start_time,
  474. self.name, exec_time, req)
  475. if req.find("/suggest?") != -1:
  476. self.res.suggest_times.AddGood(exec_time)
  477. if req.find("/cluster?") != -1:
  478. self.res.cluster_times.AddGood(exec_time)
  479. else:
  480. self.res.search_times.AddGood(exec_time)
  481. except urllib2.URLError, value:
  482. logging.info("%s: %s: error: %s query: %s", start_time,
  483. self.name, value, req)
  484. if req.find("/suggest?") != -1:
  485. self.res.suggest_times.AddError()
  486. if req.find("/cluster?") != -1:
  487. self.res.cluster_times.AddError()
  488. else:
  489. self.res.search_times.AddError()
  490. except:
  491. the_type, value, tb = sys.exc_info()
  492. logging.info("%s: %s: exception: %s %s query: %s", start_time, self.name,
  493. the_type, value, req)
  494. if req.find("/suggest?") != -1:
  495. self.res.suggest_times.AddError()
  496. if req.find("/cluster?") != -1:
  497. self.res.cluster_times.AddError()
  498. else:
  499. self.res.search_times.AddError()
  500.  
  501.  
  502. class LoadTester(object):
  503. """The load tester class."""
  504.  
  505. def Init(self):
  506. # read the queries, from either source
  507. self.queries_list = []
  508. if self.queries_filename and self.query_log_name:
  509. logging.warning("Both query file and query log were provided. "
  510. "Using query file.")
  511. if self.queries_filename:
  512. queries_file = open(self.queries_filename, 'r')
  513. logging.info("Reading queries from: %s", self.queries_filename)
  514. for line in queries_file:
  515. self.queries_list.append(line)
  516. queries_file.close()
  517. elif self.queries_xml:
  518. logging.info("reading queries from XML suggests file: %s",self.queries_xml)
  519. tree = etree.parse(self.queries_xml)
  520. for collection in tree.xpath("/collections/collection"):
  521. coll=collection.find("name").text
  522. logging.info("found collection: %s",coll)
  523. for frontend in collection.findall("frontend"):
  524. front=frontend.find("name").text
  525. logging.info("found frontend: %s",front)
  526. for query in frontend.findall("query"):
  527. quer=query.find("string").text
  528. frequency=int(query.find("frequency").text)
  529. querstr="/search?client="+front+"&output=xml_no_dtd&proxystylesheet="+front+"&site="+coll+"&q="+quer
  530. i=0
  531. while i<frequency:
  532. i+=1
  533. self.queries_list.append(querstr);
  534. else:
  535. if not self.admin_username or not self.admin_password:
  536. logging.critical("GSA admin interface login info required for fetching "
  537. "search logs")
  538. sys.exit(1)
  539. self.queries_list = self.FetchSearchLogs()
  540. # load authentication config
  541. self.auth_cfg = None
  542. if self.auth_cfg_file:
  543. f = open(self.auth_cfg_file, 'r')
  544. self.auth_cfg = yaml.load(f)
  545. f.close()
  546.  
  547. def FetchSearchLogs(self):
  548. """Fetches search logs using the appliance's admin API.
  549.  
  550. Returns:
  551. A list of query strings, e.g. ['/search?....', '/search?.....']
  552. """
  553. opener = urllib2.build_opener()
  554. # log in first
  555. post = urllib.urlencode({'Email': self.admin_username,
  556. 'Passwd': self.admin_password})
  557. req = None
  558. try:
  559. req = opener.open('https://%s:8443/accounts/ClientLogin' % self.host,
  560. post)
  561. except urllib2.HTTPError, e:
  562. logging.critical("Couldn't log in to admin API: %s", str(e))
  563. sys.exit(1)
  564. token = re.search(r"Auth=(\S+)", req.read())
  565. if not token:
  566. logging.critical("Couldn't parse auth token for GSA admin API")
  567. sys.exit(1)
  568. # request the log
  569. headers = {'Content-type': 'application/atom+xml',
  570. 'Authorization': 'GoogleLogin auth=%s' % token.group(1)}
  571. req = urllib2.Request('http://%s:8000/feeds/searchLog/%s' % (
  572. self.host, self.query_log_name), headers=headers)
  573. resp = opener.open(req).read()
  574. # extract the contents of the log
  575. doc = xml.dom.minidom.parseString(resp)
  576. content = None
  577. for node in doc.getElementsByTagName('gsa:content'):
  578. if node.getAttribute('name') == 'logContent':
  579. content = node.childNodes[0].nodeValue
  580. break
  581. if not content:
  582. logging.critical("Couldn't parse log content from %s on %s",
  583. self.query_log_name, host)
  584. sys.exit(1)
  585. queries = []
  586. for line in content.split('\n'):
  587. res = re.search(r"GET (\S+)", line)
  588. if not res:
  589. continue
  590. queries.append(res.group(1))
  591. return queries
  592.  
  593. def RunOnce(self):
  594. queries = Queue.Queue()
  595. for q in self.queries_list:
  596. queries.put(q)
  597. logging.info("Queries loaded")
  598.  
  599. # initiate results object
  600. res = Results(self.charts, self.format)
  601. thread_list = []
  602. for i in range(self.num_threads):
  603. c = Client(self.host, self.port, queries, res,
  604. self.enable_cluster, self.enable_suggest, self.rand_suggest,
  605. self.auth_cfg, self.raw)
  606. c.name = "Thread-%d" % i
  607. thread_list.append(c)
  608. c.start()
  609. # Wait for all the threads to finish before printing the summary
  610. for t in thread_list:
  611. t.join()
  612. return res
  613.  
  614. def Benchmark(self):
  615. # Loops the load testing, increasing the thread count on each iteration,
  616. # until a certain error rate is reached
  617. rates = []
  618. for n in range(1, self.max_trials + 1):
  619. self.num_threads = n * self.thread_step
  620. logging.info("Running with %d threads", self.num_threads)
  621. timeset = self.RunOnce().OverallTimes()
  622. rates.append((self.num_threads, timeset))
  623. if timeset.ErrorRate() > self.max_err_rate:
  624. break
  625. # generate report
  626. ret = "Benchmark results:\n"
  627.  
  628. if self.format == 'html':
  629. ret = '<h1>%s</h1>' % ret
  630.  
  631. for nthreads, timeset in rates:
  632. ret += " Thread count: %d\tMean latency: %.2f s\tError rate: %.2f\n" % (
  633. nthreads, timeset.MeanGood(), timeset.ErrorRate())
  634. if self.format == 'html':
  635. ret = ret.replace('\n', '<br/>')
  636.  
  637. if self.charts:
  638. latency_data = ",".join("%.2f" % r[1].MeanGood() for r in rates)
  639. max_latency = max(r[1].MeanGood() for r in rates)
  640. header = "Graph of mean latency vs. thread count:\n "
  641. if self.format == 'html':
  642. header = '<h2>%s</h2>' % header
  643. url = ("http://chart.apis.google.com/chart?"
  644. "cht=lc&"
  645. "chs=550x400&"
  646. "chxt=x,y,x,y&"
  647. "chxr=0,%d,%d,%d|1,0,%.2f&"
  648. "chxl=2:|Thread+count|3:|Mean+latency+(s)&"
  649. "chxp=2,50|3,50&"
  650. "chtt=Latency+vs+Thread+count&"
  651. "chd=t:%s&"
  652. "chds=0,%.2f") % (
  653. rates[0][0], rates[-1][0], self.thread_step,
  654. max_latency,
  655. latency_data,
  656. max_latency)
  657. if self.format == 'html':
  658. url = '<img src="%s" />' % url
  659. ret += header + url
  660.  
  661. err_rate_data = ",".join("%.4f" % r[1].ErrorRate() for r in rates)
  662. max_err_rate = max(r[1].ErrorRate() for r in rates)
  663. header = "\nGraph of error rate vs. thread count:\n "
  664. if self.format == 'html':
  665. header = '<h2>%s</h2>' % header
  666. url = ("http://chart.apis.google.com/chart?"
  667. "cht=lc&"
  668. "chs=550x400&"
  669. "chxt=x,y,x,y&"
  670. "chxr=0,%d,%d,%d|1,0,%.2f&"
  671. "chxl=2:|Thread+count|3:|Error+rate&"
  672. "chxp=2,50|3,50&"
  673. "chtt=Error+rate+vs+Thread+count&"
  674. "chd=t:%s&"
  675. "chds=0,%.2f") % (
  676. rates[0][0], rates[-1][0], self.thread_step,
  677. max_err_rate,
  678. err_rate_data,
  679. max_err_rate)
  680. if self.format == 'html':
  681. url = '<img src="%s" />' % url
  682. ret += header + url
  683. return ret
  684.  
  685. def Run(self):
  686. if self.mode == "benchmark":
  687. logging.info("Mode selected: Benchmark")
  688. return self.Benchmark()
  689. else:
  690. logging.info("Mode selected: Standard")
  691. return self.RunOnce().Summary()
  692.  
  693.  
  694. def usage():
  695. return ("load.py --queries=<queries-filename> --host=<gsa-hostname> "
  696. "[--threads=<num-threads>] [--port=<gsa-port>] [--suggest] "
  697. "[--rand_suggest] [--cluster] [--auth_cfg=<auth-cfg>] "
  698. "[--query_log=] [--admin_username=] [--admin_password=] [--charts] "
  699. "[--mode=once|benchmark] [--thread_step=<thread-step>] "
  700. "[--max_err_rate=<max-err-rate>] [--max_trials=<max-trials> "
  701. "[--format=text|html] [--output=<output_file>]")
  702.  
  703.  
  704. def main():
  705. lt = LoadTester()
  706. lt.num_threads = 3
  707. lt.port = 80
  708. lt.enable_suggest = False
  709. lt.enable_cluster = False
  710. lt.rand_suggest = False
  711. lt.host = ""
  712. lt.queries_xml = ""
  713. lt.queries_filename = ""
  714. lt.query_log_name = ""
  715. lt.charts = False
  716. lt.auth_cfg_file = ""
  717. lt.admin_username = ""
  718. lt.admin_password = ""
  719. lt.max_err_rate = 0.5
  720. lt.max_trials = 15
  721. lt.thread_step = 10
  722. lt.mode = "once"
  723. lt.format = "text"
  724. lt.raw = False
  725. output = None
  726.  
  727. try:
  728. opts, args = getopt.getopt(sys.argv[1:], None,
  729. ["host=", "port=", "threads=", "queries=",
  730. "query_log=","queries_xml=", "cluster", "suggest",
  731. "rand_suggest", "charts", "auth_cfg=",
  732. "admin_username=", "admin_password=",
  733. "mode=", "thread_step=", "max_err_rate=",
  734. "max_trials=", "format=", "output=", "raw"])
  735. except getopt.GetoptError:
  736. print usage()
  737. sys.exit(1)
  738. for opt, arg in opts:
  739. if opt == "--host":
  740. lt.host = arg
  741. if opt == "--queries_xml":
  742. lt.queries_xml = arg
  743. if opt == "--queries":
  744. lt.queries_filename = arg
  745. if opt == "--query_log":
  746. lt.query_log_name = arg
  747. if opt == "--threads":
  748. lt.num_threads = int(arg)
  749. if opt == "--port":
  750. lt.port = arg
  751. if opt == "--cluster":
  752. lt.enable_cluster = True
  753. if opt == "--suggest":
  754. lt.enable_suggest = True
  755. if opt == "--rand_suggest":
  756. lt.rand_suggest = True
  757. if opt == "--charts":
  758. lt.charts = True
  759. if opt == "--auth_cfg":
  760. lt.auth_cfg_file = arg
  761. if opt == "--admin_username":
  762. lt.admin_username = arg
  763. if opt == "--admin_password":
  764. lt.admin_password = arg
  765. if opt == "--thread_step":
  766. lt.thread_step = int(arg)
  767. if opt == "--max_err_rate":
  768. lt.max_err_rate = float(arg)
  769. if opt == "--max_trials":
  770. lt.max_trials = int(arg)
  771. if opt == "--mode":
  772. lt.mode = arg
  773. if opt == "--format":
  774. lt.format = arg
  775. if opt == "--output":
  776. output = arg
  777. if opt == "--raw":
  778. lt.raw= True
  779.  
  780. if (not lt.host and not lt.raw) or not (lt.queries_filename or lt.query_log_name or lt.queries_xml):
  781. print usage()
  782. sys.exit(1)
  783.  
  784. logging.basicConfig(level=logging.INFO,
  785. format="%(message)s")
  786.  
  787. logging.info("Initializing...")
  788. lt.Init()
  789. logging.info("Initializing complete...")
  790. report = lt.Run()
  791.  
  792. if not output:
  793. print report
  794. else:
  795. out_file = open(output, 'w')
  796. out_file.write(report + '\n')
  797. out_file.close()
  798.  
  799. if __name__ == "__main__":
  800. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement